@progress/kendo-angular-grid 24.0.0-develop.19 → 24.0.0-develop.20

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.
@@ -3,7 +3,7 @@
3
3
  * Licensed under commercial license. See LICENSE.md in the project root for more information
4
4
  *-------------------------------------------------------------------------------------------*/
5
5
  import * as i0 from '@angular/core';
6
- import { EventEmitter, Injectable, SecurityContext, InjectionToken, Optional, Inject, Input, SkipSelf, Directive, isDevMode, QueryList, ContentChildren, ContentChild, Component, forwardRef, Host, Output, HostBinding, Pipe, TemplateRef, ViewChild, ViewChildren, ChangeDetectionStrategy, Self, NgZone, HostListener, ElementRef, ViewContainerRef, ViewEncapsulation, inject, Injector, NgModule } from '@angular/core';
6
+ import { EventEmitter, Injectable, SecurityContext, InjectionToken, Optional, Inject, Input, SkipSelf, Directive, isDevMode, QueryList, ContentChildren, ContentChild, Component, forwardRef, Host, Output, HostBinding, HostListener, Pipe, TemplateRef, ViewChild, ViewChildren, ChangeDetectionStrategy, Self, NgZone, ElementRef, ViewContainerRef, ViewEncapsulation, inject, Injector, NgModule } from '@angular/core';
7
7
  import { merge, of, Subject, zip as zip$1, from, Subscription, interval, fromEvent, Observable, BehaviorSubject } from 'rxjs';
8
8
  import * as i1$3 from '@progress/kendo-angular-common';
9
9
  import { isDocumentAvailable, Keys, hasClasses as hasClasses$1, isPresent as isPresent$1, normalizeKeys, anyChanged, TemplateContextDirective, DraggableDirective, EventsOutsideAngularDirective, replaceMessagePlaceholder, isChanged as isChanged$1, KendoInput, guid, areObjectsEqual, PrefixTemplateDirective, closest as closest$1, hasObservers, ResizeSensorComponent, isFirefox, firefoxMaxHeight, closestInScope as closestInScope$1, isFocusable as isFocusable$1, getLicenseMessage, shouldShowValidationUI, WatermarkOverlayComponent, PreventableEvent as PreventableEvent$1, ResizeBatchService } from '@progress/kendo-angular-common';
@@ -3647,8 +3647,15 @@ class NavigationService {
3647
3647
  lastCellRowIndex;
3648
3648
  isShiftPressed = false;
3649
3649
  currentSelection = [];
3650
+ zonePositions = {
3651
+ 'pinned-top': null,
3652
+ 'main': null,
3653
+ 'pinned-bottom': null
3654
+ };
3655
+ currentZone = 'main';
3650
3656
  get activeDataRow() {
3651
- return Math.min(Math.max(0, this.activeRowIndex - this.meta.headerRows), this.meta.maxLogicalRowIndex);
3657
+ const pinnedTopCount = this.meta ? this.meta.pinnedTopRowsCount : 0;
3658
+ return Math.min(Math.max(0, this.activeRowIndex - this.meta.headerRows - pinnedTopCount), this.meta.maxLogicalRowIndex);
3652
3659
  }
3653
3660
  constructor(zone, domEvents, pagerContextService, scrollRequestService, groupsService, detailsService, focusRoot, editService, cd, ctx, resizeService, focusableParent) {
3654
3661
  this.zone = zone;
@@ -3769,6 +3776,64 @@ class NavigationService {
3769
3776
  isCellFocused(cell) {
3770
3777
  return this.mode === 1 /* NavigationMode.Cursor */ && this.isCellFocusable(cell);
3771
3778
  }
3779
+ /**
3780
+ * Returns the navigation zone for a given logical row index.
3781
+ * Rows inside the pinned-top container return `'pinned-top'`,
3782
+ * rows inside the pinned-bottom container return `'pinned-bottom'`,
3783
+ * and all other rows (including header rows) return `'main'`.
3784
+ */
3785
+ getZone(rowIndex) {
3786
+ if (!this.meta) {
3787
+ return 'main';
3788
+ }
3789
+ const headerRows = this.meta.headerRows;
3790
+ const pinnedTopCount = this.meta.pinnedTopRowsCount;
3791
+ const dataRows = this.meta.dataRows;
3792
+ // Pinned-top rows span [headerRows .. headerRows + pinnedTopCount - 1]
3793
+ if (pinnedTopCount > 0 && rowIndex >= headerRows && rowIndex < headerRows + pinnedTopCount) {
3794
+ return 'pinned-top';
3795
+ }
3796
+ // Pinned-bottom rows start at headerRows + pinnedTopCount + dataRows
3797
+ const pinnedBottomStart = headerRows + pinnedTopCount + dataRows;
3798
+ if (this.meta.pinnedBottomRowsCount > 0 && rowIndex >= pinnedBottomStart && rowIndex < pinnedBottomStart + this.meta.pinnedBottomRowsCount) {
3799
+ return 'pinned-bottom';
3800
+ }
3801
+ return 'main';
3802
+ }
3803
+ /**
3804
+ * Returns `true` when the given cell should carry a `tabIndex="0"` attribute as the
3805
+ * preserved Tab stop for an inactive navigation zone.
3806
+ *
3807
+ * When pinned rows are present, each zone (pinned-top, main body, pinned-bottom) independently
3808
+ * remembers its last active cell. This allows the Tab key to cycle between zones naturally
3809
+ * via DOM order, giving each zone its own Tab stop.
3810
+ */
3811
+ isCellZonePreserved(rowIndex, colIndex) {
3812
+ if (!this.alive || !this.meta) {
3813
+ return false;
3814
+ }
3815
+ if (this.meta.pinnedTopRowsCount === 0 && this.meta.pinnedBottomRowsCount === 0) {
3816
+ return false;
3817
+ }
3818
+ if (this.mode === 0 /* NavigationMode.Standby */) {
3819
+ return false;
3820
+ }
3821
+ const cellZone = this.getZone(rowIndex);
3822
+ if (cellZone === this.currentZone) {
3823
+ return false;
3824
+ }
3825
+ if (cellZone === 'pinned-top' && this.meta.pinnedTopRowsCount === 0) {
3826
+ return false;
3827
+ }
3828
+ if (cellZone === 'pinned-bottom' && this.meta.pinnedBottomRowsCount === 0) {
3829
+ return false;
3830
+ }
3831
+ const preserved = this.zonePositions[cellZone];
3832
+ const firstInZone = this.firstRowInZone(cellZone);
3833
+ const preservedRow = preserved ? preserved.rowIndex : firstInZone;
3834
+ const preservedCol = preserved ? preserved.colIndex : 0;
3835
+ return rowIndex === preservedRow && colIndex === preservedCol;
3836
+ }
3772
3837
  navigateTo(el) {
3773
3838
  if (!this.alive || !isDocumentAvailable()) {
3774
3839
  return;
@@ -3836,13 +3901,19 @@ class NavigationService {
3836
3901
  this.viewport = new NavigationViewport(firstItemIndex, lastItemIndex);
3837
3902
  if (this.meta && this.meta.isVirtual && this.activeDataRow > -1) {
3838
3903
  const dataRowIndex = this.activeDataRow;
3839
- const ahead = firstItemIndex - dataRowIndex;
3840
- const behind = dataRowIndex - lastItemIndex;
3841
- if (ahead > 0) {
3842
- this.cursor.reset(firstItemIndex + this.meta.headerRows);
3843
- }
3844
- else if (behind > 0) {
3845
- this.cursor.reset(lastItemIndex - this.meta.headerRows);
3904
+ // Pinned rows are always visible — skip viewport clamping for them
3905
+ const zone = this.getZone(this.activeRowIndex);
3906
+ const isPinnedRow = zone === 'pinned-top' || zone === 'pinned-bottom';
3907
+ if (!isPinnedRow) {
3908
+ const ahead = firstItemIndex - dataRowIndex;
3909
+ const behind = dataRowIndex - lastItemIndex;
3910
+ const logicalOffset = this.meta.headerRows + this.meta.pinnedTopRowsCount;
3911
+ if (ahead > 0) {
3912
+ this.cursor.reset(firstItemIndex + logicalOffset);
3913
+ }
3914
+ else if (behind > 0) {
3915
+ this.cursor.reset(lastItemIndex + logicalOffset);
3916
+ }
3846
3917
  }
3847
3918
  }
3848
3919
  }
@@ -4013,11 +4084,36 @@ class NavigationService {
4013
4084
  const isRowReorderable = this.ctx.grid.rowReorderable;
4014
4085
  switch (code) {
4015
4086
  case Keys.ArrowDown:
4016
- case Keys.ArrowUp:
4087
+ case Keys.ArrowUp: {
4017
4088
  if (rowspan > 1) {
4018
4089
  rowspanOffset = this.calculateRowspanOffset(dir, rowspan);
4019
4090
  step += rowspanOffset;
4020
4091
  }
4092
+ const isDown = code === Keys.ArrowDown;
4093
+ const currentZone = this.meta ? this.getZone(this.activeRowIndex) : 'main';
4094
+ const atPinnedBoundary = this.meta?.isVirtual && ((isDown && currentZone === 'pinned-top' && this.activeRowIndex === this.firstRowInZone('main') - 1) ||
4095
+ (!isDown && currentZone === 'pinned-bottom' && this.activeRowIndex === this.firstRowInZone('pinned-bottom')));
4096
+ if (atPinnedBoundary && !args.shiftKey) {
4097
+ const targetRow = isDown
4098
+ ? this.firstRowInZone('main')
4099
+ : this.firstRowInZone('pinned-bottom') - 1;
4100
+ const targetModelRow = this.model.findRow(targetRow);
4101
+ let targetColIndex;
4102
+ if (!targetModelRow) {
4103
+ targetColIndex = 0;
4104
+ this.virtualCell = true;
4105
+ }
4106
+ else if (targetModelRow.groupItem) {
4107
+ targetColIndex = 0;
4108
+ }
4109
+ else {
4110
+ targetColIndex = this.cursor.cell?.colIndex ?? 0;
4111
+ }
4112
+ this.cursor.reset(targetRow, targetColIndex);
4113
+ preventDefault = true;
4114
+ this.lastCellRowIndex = this.activeRowIndex;
4115
+ break;
4116
+ }
4021
4117
  if (args.shiftKey && !(isDragCell && isRowReorderable)) {
4022
4118
  if (this.ctx.grid.blockArrowSelection) {
4023
4119
  return;
@@ -4034,6 +4130,7 @@ class NavigationService {
4034
4130
  }
4035
4131
  this.lastCellRowIndex = this.activeRowIndex;
4036
4132
  break;
4133
+ }
4037
4134
  case Keys.ArrowRight:
4038
4135
  case Keys.ArrowLeft:
4039
4136
  if (args.altKey && !args.shiftKey && !args.ctrlKey && !args.metaKey &&
@@ -4283,10 +4380,36 @@ class NavigationService {
4283
4380
  this.cellKeydown.emit(args);
4284
4381
  return true;
4285
4382
  }
4383
+ firstRowInZone(zone) {
4384
+ if (!this.meta) {
4385
+ return 0;
4386
+ }
4387
+ const headerRows = this.meta.headerRows;
4388
+ const pinnedTopCount = this.meta.pinnedTopRowsCount;
4389
+ const dataRows = this.meta.dataRows;
4390
+ switch (zone) {
4391
+ case 'pinned-top': return headerRows;
4392
+ case 'main': return headerRows + pinnedTopCount;
4393
+ case 'pinned-bottom': return headerRows + pinnedTopCount + dataRows;
4394
+ }
4395
+ }
4286
4396
  onCursorChanges(args) {
4397
+ if (this.meta) {
4398
+ const newZone = this.getZone(args.rowIndex);
4399
+ if (newZone !== this.currentZone) {
4400
+ // Save the position we're leaving as the preserved Tab stop for that zone
4401
+ if (args.prevRowIndex >= 0) {
4402
+ this.zonePositions[this.currentZone] = { rowIndex: args.prevRowIndex, colIndex: args.prevColIndex };
4403
+ }
4404
+ this.currentZone = newZone;
4405
+ }
4406
+ }
4287
4407
  this.activeRowIndex = args.rowIndex;
4288
4408
  const dataRowIndex = this.activeDataRow;
4289
- if (this.meta && (this.meta.isVirtual &&
4409
+ // Pinned rows are always visible — skip virtual scroll adjustment
4410
+ const zone = this.meta ? this.getZone(args.rowIndex) : 'main';
4411
+ const isPinnedRow = zone === 'pinned-top' || zone === 'pinned-bottom';
4412
+ if (!isPinnedRow && this.meta && (this.meta.isVirtual &&
4290
4413
  args.rowIndex >= this.meta.headerRows &&
4291
4414
  args.rowIndex <= this.meta.maxLogicalRowIndex - this.meta.footerRow &&
4292
4415
  this.viewport &&
@@ -4294,6 +4417,12 @@ class NavigationService {
4294
4417
  dataRowIndex > -1)) {
4295
4418
  this.scrollRequestService.scrollTo({ row: dataRowIndex });
4296
4419
  }
4420
+ if (!isPinnedRow && this.meta && !this.meta.isVirtual && !this.meta.hasPager) {
4421
+ const prevZone = this.meta ? this.getZone(args.prevRowIndex) : 'main';
4422
+ if (prevZone === 'pinned-top' || prevZone === 'pinned-bottom') {
4423
+ this.scrollRequestService.scrollTo({ row: dataRowIndex });
4424
+ }
4425
+ }
4297
4426
  if (this.meta.virtualColumns && args.colIndex >= this.meta.columns.lockedLeafColumns.length) {
4298
4427
  const cell = this.activeCell;
4299
4428
  const { start, end } = this.model.cellRange(cell);
@@ -5417,6 +5546,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
5417
5546
  type: Injectable
5418
5547
  }] });
5419
5548
 
5549
+ /**
5550
+ * @hidden
5551
+ * When provided as `true`, LogicalCellDirective instances skip all
5552
+ * registration, focus management, and keyboard handling. Used by the
5553
+ * sticky-group overlay container which manages its own focus independently.
5554
+ */
5555
+ const SKIP_CELL_NAVIGATION = new InjectionToken('skip-cell-navigation');
5556
+
5420
5557
  let id$3 = 0;
5421
5558
  function nextId$1() {
5422
5559
  return id$3++;
@@ -5433,6 +5570,7 @@ class LogicalCellDirective {
5433
5570
  renderer;
5434
5571
  zone;
5435
5572
  cellContext;
5573
+ skipNavigation;
5436
5574
  logicalColIndex;
5437
5575
  logicalRowIndex;
5438
5576
  logicalSlaveCell = false;
@@ -5446,6 +5584,9 @@ class LogicalCellDirective {
5446
5584
  headerLabelText;
5447
5585
  uid = nextId$1();
5448
5586
  get id() {
5587
+ if (this.skipNavigation) {
5588
+ return undefined;
5589
+ }
5449
5590
  if (!this.logicalSlaveCell && this.columnInfoService.isLocked) {
5450
5591
  return this.idService.cellId(this.logicalRowIndex, this.logicalColIndex);
5451
5592
  }
@@ -5463,7 +5604,7 @@ class LogicalCellDirective {
5463
5604
  return this.logicalColIndex + 1;
5464
5605
  }
5465
5606
  navigationChange;
5466
- constructor(focusGroup, element, columnInfoService, idService, navigationService, renderer, zone, cellContext) {
5607
+ constructor(focusGroup, element, columnInfoService, idService, navigationService, renderer, zone, cellContext, skipNavigation) {
5467
5608
  this.focusGroup = focusGroup;
5468
5609
  this.element = element;
5469
5610
  this.columnInfoService = columnInfoService;
@@ -5472,15 +5613,24 @@ class LogicalCellDirective {
5472
5613
  this.renderer = renderer;
5473
5614
  this.zone = zone;
5474
5615
  this.cellContext = cellContext;
5616
+ this.skipNavigation = skipNavigation;
5617
+ }
5618
+ onMouseDown() {
5619
+ if (this.skipNavigation) {
5620
+ return;
5621
+ }
5622
+ if (this.logicalSlaveCell && this.navigationService.tableEnabled) {
5623
+ this.navigationService.focusCell(this.logicalRowIndex, this.logicalColIndex);
5624
+ }
5475
5625
  }
5476
5626
  ngOnInit() {
5477
- if (!this.navigationService.tableEnabled) {
5627
+ if (this.skipNavigation || !this.navigationService.tableEnabled) {
5478
5628
  return;
5479
5629
  }
5480
5630
  this.navigationChange = this.navigationService.changes.subscribe((e) => this.onNavigationChange(e));
5481
5631
  }
5482
5632
  ngDoCheck() {
5483
- if (!this.navigationService.tableEnabled || this.logicalColIndex === -1) {
5633
+ if (this.skipNavigation || !this.navigationService.tableEnabled || this.logicalColIndex === -1) {
5484
5634
  return;
5485
5635
  }
5486
5636
  if (this.cellContext) {
@@ -5489,7 +5639,7 @@ class LogicalCellDirective {
5489
5639
  this.registerNoChanges();
5490
5640
  }
5491
5641
  ngOnChanges(changes) {
5492
- if (!this.navigationService.tableEnabled) {
5642
+ if (this.skipNavigation || !this.navigationService.tableEnabled) {
5493
5643
  return;
5494
5644
  }
5495
5645
  const keys = Object.keys(changes);
@@ -5506,6 +5656,9 @@ class LogicalCellDirective {
5506
5656
  this.updateElement();
5507
5657
  }
5508
5658
  ngOnDestroy() {
5659
+ if (this.skipNavigation) {
5660
+ return;
5661
+ }
5509
5662
  if (this.navigationChange) {
5510
5663
  this.navigationChange.unsubscribe();
5511
5664
  }
@@ -5562,13 +5715,14 @@ class LogicalCellDirective {
5562
5715
  }
5563
5716
  }
5564
5717
  isFocusable() {
5565
- return this.navigationService.isCellFocusable(this);
5718
+ return this.navigationService.isCellFocusable(this) ||
5719
+ this.navigationService.isCellZonePreserved(this.logicalRowIndex, this.logicalColIndex);
5566
5720
  }
5567
5721
  isFocused() {
5568
5722
  return this.navigationService.isCellFocused(this);
5569
5723
  }
5570
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: LogicalCellDirective, deps: [{ token: FocusGroup }, { token: i0.ElementRef }, { token: ColumnInfoService }, { token: IdService }, { token: NavigationService }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: CELL_CONTEXT, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
5571
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: LogicalCellDirective, isStandalone: true, selector: "[kendoGridLogicalCell]", inputs: { logicalColIndex: "logicalColIndex", logicalRowIndex: "logicalRowIndex", logicalSlaveCell: "logicalSlaveCell", colIndex: "colIndex", colSpan: "colSpan", rowSpan: "rowSpan", groupItem: "groupItem", dataRowIndex: "dataRowIndex", dataItem: "dataItem", detailExpandCell: "detailExpandCell", headerLabelText: "headerLabelText" }, host: { properties: { "attr.id": "this.id", "attr.rowspan": "this.cellRowspan", "class.k-table-td-row-span": "this.rowspanClass", "attr.aria-colindex": "this.ariaColIndex" } }, providers: [{
5724
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: LogicalCellDirective, deps: [{ token: FocusGroup }, { token: i0.ElementRef }, { token: ColumnInfoService }, { token: IdService }, { token: NavigationService }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: CELL_CONTEXT, optional: true }, { token: SKIP_CELL_NAVIGATION, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
5725
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: LogicalCellDirective, isStandalone: true, selector: "[kendoGridLogicalCell]", inputs: { logicalColIndex: "logicalColIndex", logicalRowIndex: "logicalRowIndex", logicalSlaveCell: "logicalSlaveCell", colIndex: "colIndex", colSpan: "colSpan", rowSpan: "rowSpan", groupItem: "groupItem", dataRowIndex: "dataRowIndex", dataItem: "dataItem", detailExpandCell: "detailExpandCell", headerLabelText: "headerLabelText" }, host: { listeners: { "mousedown": "onMouseDown()" }, properties: { "attr.id": "this.id", "attr.rowspan": "this.cellRowspan", "class.k-table-td-row-span": "this.rowspanClass", "attr.aria-colindex": "this.ariaColIndex" } }, providers: [{
5572
5726
  provide: FocusGroup,
5573
5727
  deps: [FocusRoot],
5574
5728
  useClass: FocusGroup
@@ -5590,6 +5744,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
5590
5744
  }, {
5591
5745
  type: Inject,
5592
5746
  args: [CELL_CONTEXT]
5747
+ }] }, { type: undefined, decorators: [{
5748
+ type: Optional
5749
+ }, {
5750
+ type: Inject,
5751
+ args: [SKIP_CELL_NAVIGATION]
5593
5752
  }] }], propDecorators: { logicalColIndex: [{
5594
5753
  type: Input
5595
5754
  }], logicalRowIndex: [{
@@ -5624,6 +5783,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
5624
5783
  }], ariaColIndex: [{
5625
5784
  type: HostBinding,
5626
5785
  args: ['attr.aria-colindex']
5786
+ }], onMouseDown: [{
5787
+ type: HostListener,
5788
+ args: ['mousedown']
5627
5789
  }] } });
5628
5790
 
5629
5791
  let id$2 = 0;
@@ -21655,9 +21817,13 @@ class CellComponent {
21655
21817
  ctx;
21656
21818
  detailsService;
21657
21819
  localization;
21820
+ elementRef;
21821
+ ngZone;
21822
+ renderer;
21658
21823
  rowPinService;
21659
21824
  cellContext;
21660
21825
  inPinContainer;
21826
+ scrollSyncService;
21661
21827
  get commandCellClass() {
21662
21828
  return this.isCommand(this.column);
21663
21829
  }
@@ -21667,6 +21833,13 @@ class CellComponent {
21667
21833
  get dragRowHandleLabel() {
21668
21834
  return isRowReorderColumn(this.column) ? this.ctx.localization.get('dragRowHandleLabel') : undefined;
21669
21835
  }
21836
+ get rowPinLabel() {
21837
+ const isRowPinned = this.isRowPinnedTop || this.isRowPinnedBottom;
21838
+ if (isRowPinColumn(this.column)) {
21839
+ return isRowPinned ? this.ctx.localization.get('rowUnpinLabel') : this.ctx.localization.get('rowPinLabel');
21840
+ }
21841
+ return undefined;
21842
+ }
21670
21843
  pinContextMenu;
21671
21844
  pinContextMenuTarget;
21672
21845
  column;
@@ -21698,6 +21871,7 @@ class CellComponent {
21698
21871
  }
21699
21872
  if (gridPinnable === true || gridPinnable === 'both') {
21700
21873
  this.pinContextMenu.show(this.pinContextMenuTarget.nativeElement);
21874
+ this.subscribeToScrollClose();
21701
21875
  }
21702
21876
  else {
21703
21877
  let menuItem;
@@ -21714,6 +21888,7 @@ class CellComponent {
21714
21888
  }
21715
21889
  else {
21716
21890
  this.pinContextMenu.show(this.pinContextMenuTarget.nativeElement);
21891
+ this.subscribeToScrollClose();
21717
21892
  }
21718
21893
  const args = {
21719
21894
  menuItem,
@@ -21858,7 +22033,12 @@ class CellComponent {
21858
22033
  if (!callback) {
21859
22034
  return true;
21860
22035
  }
21861
- return callback({ dataItem: this.dataItem, index: this.rowIndex });
22036
+ // For pinned container rows, use the stable pinnedIndex (render position within the
22037
+ // container) rather than rowIndex, which fluctuates as the main table virtual-scrolls.
22038
+ const index = this.inPinContainer && this.item?.pinnedIndex !== undefined
22039
+ ? this.item.pinnedIndex
22040
+ : this.rowIndex;
22041
+ return callback({ dataItem: this.dataItem, index });
21862
22042
  }
21863
22043
  isRowSelectable(column) {
21864
22044
  const currentColumn = column || this.column;
@@ -21875,19 +22055,57 @@ class CellComponent {
21875
22055
  _templateContext = {};
21876
22056
  _editTemplateContext = {};
21877
22057
  _rowTemplateContext = {};
21878
- constructor(editService, idService, ctx, detailsService, localization, rowPinService, cellContext, inPinContainer) {
22058
+ keyDownSub;
22059
+ scrollCloseSub = new Subscription();
22060
+ constructor(editService, idService, ctx, detailsService, localization, elementRef, ngZone, renderer, rowPinService, cellContext, inPinContainer, scrollSyncService) {
21879
22061
  this.editService = editService;
21880
22062
  this.idService = idService;
21881
22063
  this.ctx = ctx;
21882
22064
  this.detailsService = detailsService;
21883
22065
  this.localization = localization;
22066
+ this.elementRef = elementRef;
22067
+ this.ngZone = ngZone;
22068
+ this.renderer = renderer;
21884
22069
  this.rowPinService = rowPinService;
21885
22070
  this.cellContext = cellContext;
21886
22071
  this.inPinContainer = inPinContainer;
22072
+ this.scrollSyncService = scrollSyncService;
22073
+ }
22074
+ ngOnInit() {
22075
+ this.ngZone.runOutsideAngular(() => {
22076
+ this.keyDownSub = this.renderer.listen(this.elementRef.nativeElement, 'keydown', this.onHostKeydown.bind(this));
22077
+ });
22078
+ }
22079
+ ngOnDestroy() {
22080
+ if (this.keyDownSub) {
22081
+ this.keyDownSub();
22082
+ }
22083
+ this.scrollCloseSub.unsubscribe();
21887
22084
  }
21888
22085
  ngDoCheck() {
21889
22086
  this.updateCellContext();
21890
22087
  }
22088
+ subscribeToScrollClose() {
22089
+ this.scrollCloseSub.unsubscribe();
22090
+ this.scrollCloseSub = new Subscription();
22091
+ if (this.scrollSyncService) {
22092
+ this.scrollCloseSub.add(this.scrollSyncService.changes.pipe(skip(1)).subscribe(() => this.pinContextMenu?.hide()));
22093
+ }
22094
+ if (this.ctx.grid?.contentScroll) {
22095
+ this.scrollCloseSub.add(this.ctx.grid.contentScroll.subscribe(() => this.pinContextMenu?.hide()));
22096
+ }
22097
+ }
22098
+ onHostKeydown(e) {
22099
+ if (e.code === Keys.Enter || e.code === Keys.NumpadEnter) {
22100
+ if (!isDocumentAvailable() || document.activeElement !== this.elementRef.nativeElement) {
22101
+ return;
22102
+ }
22103
+ if (!this.isRowPinColumn() || !this.isRowPinnable || this.isNew) {
22104
+ return;
22105
+ }
22106
+ this.onPinIconClick();
22107
+ }
22108
+ }
21891
22109
  isCommand(column) {
21892
22110
  return column instanceof CommandColumnComponent;
21893
22111
  }
@@ -21906,8 +22124,8 @@ class CellComponent {
21906
22124
  this.cellContext.inPinContainer = !!this.inPinContainer;
21907
22125
  }
21908
22126
  }
21909
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CellComponent, deps: [{ token: EditService }, { token: IdService }, { token: ContextService }, { token: DetailsService }, { token: i1$2.LocalizationService }, { token: RowPinService, optional: true }, { token: CELL_CONTEXT, optional: true }, { token: IS_PIN_CONTAINER, optional: true }], target: i0.ɵɵFactoryTarget.Component });
21910
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: CellComponent, isStandalone: true, selector: "[kendoGridCell]", inputs: { column: "column", columns: "columns", columnIndex: "columnIndex", isNew: "isNew", isLoading: "isLoading", isVirtual: "isVirtual", loadingTemplate: "loadingTemplate", detailTemplate: "detailTemplate", item: "item", rowIndex: "rowIndex", dataItem: "dataItem" }, host: { properties: { "class.k-command-cell": "this.commandCellClass", "class.k-drag-cell": "this.dragHandleCellClass", "class.k-touch-action-none": "this.dragHandleCellClass", "attr.aria-label": "this.dragRowHandleLabel" } }, viewQueries: [{ propertyName: "pinContextMenu", first: true, predicate: ["pinContextMenu"], descendants: true }, { propertyName: "pinContextMenuTarget", first: true, predicate: ["target"], descendants: true, read: ElementRef }], ngImport: i0, template: `
22127
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CellComponent, deps: [{ token: EditService }, { token: IdService }, { token: ContextService }, { token: DetailsService }, { token: i1$2.LocalizationService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: RowPinService, optional: true }, { token: CELL_CONTEXT, optional: true }, { token: IS_PIN_CONTAINER, optional: true }, { token: ScrollSyncService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
22128
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: CellComponent, isStandalone: true, selector: "[kendoGridCell]", inputs: { column: "column", columns: "columns", columnIndex: "columnIndex", isNew: "isNew", isLoading: "isLoading", isVirtual: "isVirtual", loadingTemplate: "loadingTemplate", detailTemplate: "detailTemplate", item: "item", rowIndex: "rowIndex", dataItem: "dataItem" }, host: { properties: { "class.k-command-cell": "this.commandCellClass", "class.k-drag-cell": "this.dragHandleCellClass", "class.k-touch-action-none": "this.dragHandleCellClass", "attr.aria-label": "this.rowPinLabel" } }, viewQueries: [{ propertyName: "pinContextMenu", first: true, predicate: ["pinContextMenu"], descendants: true }, { propertyName: "pinContextMenuTarget", first: true, predicate: ["target"], descendants: true, read: ElementRef }], ngImport: i0, template: `
21911
22129
  @if (isStackedLayoutMode) {
21912
22130
  <div class="k-grid-stack-row"
21913
22131
  [ngClass]="stackedRowClass"
@@ -22408,7 +22626,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
22408
22626
  imports: [NgTemplateOutlet, FocusableDirective, SelectionCheckboxDirective, TemplateContextDirective,
22409
22627
  IconWrapperComponent, NumericTextBoxComponent, DatePickerComponent, FieldAccessorPipe, ReactiveFormsModule, CheckBoxComponent, TextBoxComponent, NgClass, NgStyle, ButtonComponent, ContextMenuComponent, EventsOutsideAngularDirective]
22410
22628
  }]
22411
- }], ctorParameters: () => [{ type: EditService }, { type: IdService }, { type: ContextService }, { type: DetailsService }, { type: i1$2.LocalizationService }, { type: RowPinService, decorators: [{
22629
+ }], ctorParameters: () => [{ type: EditService }, { type: IdService }, { type: ContextService }, { type: DetailsService }, { type: i1$2.LocalizationService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: RowPinService, decorators: [{
22412
22630
  type: Optional
22413
22631
  }] }, { type: undefined, decorators: [{
22414
22632
  type: Optional
@@ -22420,6 +22638,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
22420
22638
  }, {
22421
22639
  type: Inject,
22422
22640
  args: [IS_PIN_CONTAINER]
22641
+ }] }, { type: ScrollSyncService, decorators: [{
22642
+ type: Optional
22423
22643
  }] }], propDecorators: { commandCellClass: [{
22424
22644
  type: HostBinding,
22425
22645
  args: ['class.k-command-cell']
@@ -22432,6 +22652,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
22432
22652
  }], dragRowHandleLabel: [{
22433
22653
  type: HostBinding,
22434
22654
  args: ['attr.aria-label']
22655
+ }], rowPinLabel: [{
22656
+ type: HostBinding,
22657
+ args: ['attr.aria-label']
22435
22658
  }], pinContextMenu: [{
22436
22659
  type: ViewChild,
22437
22660
  args: ['pinContextMenu']
@@ -22522,6 +22745,9 @@ class TableBodyComponent {
22522
22745
  hostClass = true;
22523
22746
  groupHeaderSlaveCellsCount;
22524
22747
  groupHeaderColumns;
22748
+ // Cached from navigationService.metadata so logicalRowIndex
22749
+ // returns a stable value within a single CD check (avoids NG0100).
22750
+ cachedDataRows = 0;
22525
22751
  clickSubscription;
22526
22752
  touchSubscription;
22527
22753
  l10nSubscription;
@@ -22583,7 +22809,8 @@ class TableBodyComponent {
22583
22809
  return this.ctx.localization.get(messageKey);
22584
22810
  }
22585
22811
  isOdd(item) {
22586
- return item.index % 2 !== 0;
22812
+ const index = this.isPinContainer && item.pinnedIndex !== undefined ? item.pinnedIndex : item.index;
22813
+ return index % 2 !== 0;
22587
22814
  }
22588
22815
  isSelectable(args) {
22589
22816
  const rowSelectable = this.isRowSelectable(args);
@@ -22603,6 +22830,9 @@ class TableBodyComponent {
22603
22830
  return this.virtualColumns ? index : item;
22604
22831
  }
22605
22832
  ngDoCheck() {
22833
+ if (this.pinPosition === 'bottom') {
22834
+ this.cachedDataRows = this.navigationService.metadata?.dataRows ?? 0;
22835
+ }
22606
22836
  if (this.rowsToRender) {
22607
22837
  this.rowsToRender.forEach((item) => {
22608
22838
  if (item.type === 'data') {
@@ -22644,8 +22874,7 @@ class TableBodyComponent {
22644
22874
  return headerRowCount + rowIndex + 1;
22645
22875
  }
22646
22876
  if (this.pinPosition === 'bottom') {
22647
- const dataRows = this.navigationService.metadata?.dataRows ?? 0;
22648
- return headerRowCount + pinnedTopCount + dataRows + rowIndex + 1;
22877
+ return headerRowCount + pinnedTopCount + this.cachedDataRows + rowIndex + 1;
22649
22878
  }
22650
22879
  const skip = this.skip + (this.ctx.scroller?.virtualSkip ?? 0);
22651
22880
  let pos = rowIndex + skip;
@@ -23040,12 +23269,13 @@ class TableBodyComponent {
23040
23269
  [groupHeaderColumns]="groupHeaderColumns"
23041
23270
  [rowIndex]="rowIndex + 1"
23042
23271
  [totalColumnsCount]="totalColumnsCount"
23272
+ [attr.data-group-index]="$any(item).index"
23043
23273
  kendoGridLogicalRow
23044
23274
  [logicalRowIndex]="logicalRowIndex(rowIndex)"
23045
23275
  [logicalSlaveRow]="lockedColumnsCount > 0 && !isStackedMode"
23046
23276
  [totalColumns]="totalColumns"
23047
23277
  [logicalCellsCount]="columns.length"
23048
- [logicalSlaveCellsCount]="groupHeaderSlaveCellsCount">
23278
+ [logicalSlaveCellsCount]="0">
23049
23279
  </tr>
23050
23280
  }
23051
23281
  @if (item.showDataItem) {
@@ -23390,12 +23620,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
23390
23620
  [groupHeaderColumns]="groupHeaderColumns"
23391
23621
  [rowIndex]="rowIndex + 1"
23392
23622
  [totalColumnsCount]="totalColumnsCount"
23623
+ [attr.data-group-index]="$any(item).index"
23393
23624
  kendoGridLogicalRow
23394
23625
  [logicalRowIndex]="logicalRowIndex(rowIndex)"
23395
23626
  [logicalSlaveRow]="lockedColumnsCount > 0 && !isStackedMode"
23396
23627
  [totalColumns]="totalColumns"
23397
23628
  [logicalCellsCount]="columns.length"
23398
- [logicalSlaveCellsCount]="groupHeaderSlaveCellsCount">
23629
+ [logicalSlaveCellsCount]="0">
23399
23630
  </tr>
23400
23631
  }
23401
23632
  @if (item.showDataItem) {
@@ -24569,8 +24800,8 @@ const packageMetadata = {
24569
24800
  productName: 'Kendo UI for Angular',
24570
24801
  productCode: 'KENDOUIANGULAR',
24571
24802
  productCodes: ['KENDOUIANGULAR'],
24572
- publishDate: 1778145745,
24573
- version: '24.0.0-develop.19',
24803
+ publishDate: 1778149554,
24804
+ version: '24.0.0-develop.20',
24574
24805
  licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/'
24575
24806
  };
24576
24807
 
@@ -27601,6 +27832,1668 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
27601
27832
  type: Injectable
27602
27833
  }], ctorParameters: () => [{ type: RowspanService }, { type: GroupsService }, { type: DetailsService }, { type: ContextService }] });
27603
27834
 
27835
+ /**
27836
+ * @hidden
27837
+ * Builds a map of group ranges from the flat data array.
27838
+ * Each group header maps to its footer and child range.
27839
+ */
27840
+ function buildGroupRangeMap(flatDataArray) {
27841
+ const ranges = [];
27842
+ const stack = [];
27843
+ for (let i = 0; i < flatDataArray.length; i++) {
27844
+ const item = flatDataArray[i];
27845
+ if (item.type === 'group') {
27846
+ // A new header at level N closes any open groups at level >= N
27847
+ for (let s = stack.length - 1; s >= 0; s--) {
27848
+ if (stack[s].level >= item.level) {
27849
+ const headerInfo = stack[s];
27850
+ ranges.push({
27851
+ headerIndex: headerInfo.headerIndex,
27852
+ footerIndex: null,
27853
+ firstChildIndex: headerInfo.headerIndex + 1,
27854
+ lastChildIndex: i - 1,
27855
+ level: headerInfo.level
27856
+ });
27857
+ stack.splice(s, 1);
27858
+ }
27859
+ }
27860
+ stack.push({ headerIndex: i, level: item.level });
27861
+ }
27862
+ else if (item.type === 'footer') {
27863
+ for (let s = stack.length - 1; s >= 0; s--) {
27864
+ if (stack[s].level === item.level) {
27865
+ const headerInfo = stack[s];
27866
+ ranges.push({
27867
+ headerIndex: headerInfo.headerIndex,
27868
+ footerIndex: i,
27869
+ firstChildIndex: headerInfo.headerIndex + 1,
27870
+ lastChildIndex: i - 1,
27871
+ level: item.level
27872
+ });
27873
+ stack.splice(s, 1);
27874
+ break;
27875
+ }
27876
+ }
27877
+ }
27878
+ }
27879
+ // Close remaining open groups that extend to the end of the data
27880
+ for (const headerInfo of stack) {
27881
+ ranges.push({
27882
+ headerIndex: headerInfo.headerIndex,
27883
+ footerIndex: null,
27884
+ firstChildIndex: headerInfo.headerIndex + 1,
27885
+ lastChildIndex: flatDataArray.length - 1,
27886
+ level: headerInfo.level
27887
+ });
27888
+ }
27889
+ const map = new Map();
27890
+ for (const range of ranges) {
27891
+ map.set(range.headerIndex, range);
27892
+ }
27893
+ return map;
27894
+ }
27895
+ /**
27896
+ * @hidden
27897
+ * Selects which group headers should be sticky at the top of the viewport.
27898
+ * A header is sticky when it is scrolled above the viewport but its group
27899
+ * children are still visible below.
27900
+ */
27901
+ function computeStickyItems(flatDataArray, groupRanges, firstVisibleIndex, lastVisibleIndex, rawFirstVisibleIndex) {
27902
+ const footerThreshold = rawFirstVisibleIndex ?? firstVisibleIndex;
27903
+ const matchingRanges = [];
27904
+ for (const range of groupRanges.values()) {
27905
+ if (range.headerIndex < firstVisibleIndex) {
27906
+ // The sticky header stays as a candidate until the next group
27907
+ // header pushes it out. This matches the push boundary logic.
27908
+ const groupEnd = range.footerIndex !== null
27909
+ ? range.footerIndex + 1
27910
+ : range.lastChildIndex + 1;
27911
+ if (groupEnd > range.headerIndex && groupEnd >= footerThreshold) {
27912
+ matchingRanges.push(range);
27913
+ }
27914
+ }
27915
+ }
27916
+ // Per level, pick the header with the highest index (closest to viewport)
27917
+ const bestPerLevel = new Map();
27918
+ for (const range of matchingRanges) {
27919
+ const existing = bestPerLevel.get(range.level);
27920
+ if (!existing || range.headerIndex > existing.headerIndex) {
27921
+ bestPerLevel.set(range.level, range);
27922
+ }
27923
+ }
27924
+ // Sort ascending by level: shallowest at top
27925
+ const sortedLevels = Array.from(bestPerLevel.keys()).sort((a, b) => a - b);
27926
+ const result = [];
27927
+ for (const level of sortedLevels) {
27928
+ const range = bestPerLevel.get(level);
27929
+ result.push({ item: flatDataArray[range.headerIndex], flatIndex: range.headerIndex });
27930
+ }
27931
+ return result;
27932
+ }
27933
+ /**
27934
+ * @hidden
27935
+ * Selects which group footers should be sticky at the bottom of the viewport.
27936
+ * A footer is sticky when it is scrolled below the viewport but its group's
27937
+ * header or first child is still visible above.
27938
+ */
27939
+ function computeStickyFooterItems(flatDataArray, groupRanges, firstVisibleIndex, lastVisibleIndex) {
27940
+ const matchingRanges = [];
27941
+ for (const range of groupRanges.values()) {
27942
+ if (range.footerIndex === null) {
27943
+ continue;
27944
+ }
27945
+ const match = range.footerIndex > lastVisibleIndex && range.headerIndex <= lastVisibleIndex;
27946
+ if (match) {
27947
+ matchingRanges.push(range);
27948
+ }
27949
+ }
27950
+ // Per level, pick the footer closest to viewport bottom (lowest footerIndex)
27951
+ const bestPerLevel = new Map();
27952
+ for (const range of matchingRanges) {
27953
+ const existing = bestPerLevel.get(range.level);
27954
+ if (!existing || range.footerIndex < existing.footerIndex) {
27955
+ bestPerLevel.set(range.level, range);
27956
+ }
27957
+ }
27958
+ // Sort descending by level: deepest at top (closest to content), shallowest at bottom
27959
+ const sortedLevels = Array.from(bestPerLevel.keys()).sort((a, b) => b - a);
27960
+ const result = [];
27961
+ for (const level of sortedLevels) {
27962
+ const range = bestPerLevel.get(level);
27963
+ result.push({ item: flatDataArray[range.footerIndex], flatIndex: range.footerIndex });
27964
+ }
27965
+ return result;
27966
+ }
27967
+ /**
27968
+ * @hidden
27969
+ * Returns true if the displayed groups differ between current and previous arrays.
27970
+ */
27971
+ function hasStickyItemsChanged(current, prev) {
27972
+ if (current.length !== prev.length) {
27973
+ return true;
27974
+ }
27975
+ for (let i = 0; i < current.length; i++) {
27976
+ if (current[i].flatIndex !== prev[i].flatIndex) {
27977
+ return true;
27978
+ }
27979
+ }
27980
+ return false;
27981
+ }
27982
+ /**
27983
+ * @hidden
27984
+ * Computes push offsets for sticky header overlay rows.
27985
+ * When a group's boundary approaches the sticky slot, the header is pushed upward.
27986
+ */
27987
+ function computeHeaderPush(indices, getRowHeight, getRowOffset, scrollTop, groupRanges) {
27988
+ const offsets = [];
27989
+ let cumHeight = 0;
27990
+ for (const flatIdx of indices) {
27991
+ const rowH = getRowHeight(flatIdx);
27992
+ let push = 0;
27993
+ const range = groupRanges.get(flatIdx);
27994
+ if (range) {
27995
+ const nextHeaderIndex = range.footerIndex !== null
27996
+ ? range.footerIndex + 1
27997
+ : range.lastChildIndex + 1;
27998
+ const boundaryOffset = getRowOffset(nextHeaderIndex);
27999
+ if (boundaryOffset !== undefined) {
28000
+ const slotBottom = cumHeight + rowH;
28001
+ const boundaryRelative = boundaryOffset - scrollTop;
28002
+ if (boundaryRelative < slotBottom) {
28003
+ push = boundaryRelative - slotBottom;
28004
+ }
28005
+ }
28006
+ }
28007
+ offsets.push(push);
28008
+ cumHeight += Math.max(rowH + push, 0);
28009
+ }
28010
+ return { totalHeight: cumHeight, offsets };
28011
+ }
28012
+ /**
28013
+ * @hidden
28014
+ * Computes push offsets for sticky footer overlay rows.
28015
+ * When a group's header approaches the sticky footer slot, the footer is pushed downward.
28016
+ */
28017
+ function computeFooterPush(indices, getRowHeight, getRowOffset, scrollTop, viewportHeight, footerRanges) {
28018
+ const offsets = new Array(indices.length).fill(0);
28019
+ let cumHeight = 0;
28020
+ // Footers are rendered bottom-up; iterate in reverse
28021
+ for (let i = indices.length - 1; i >= 0; i--) {
28022
+ const flatIdx = indices[i];
28023
+ const rowH = getRowHeight(flatIdx);
28024
+ let push = 0;
28025
+ const range = footerRanges.get(flatIdx);
28026
+ if (range) {
28027
+ const boundaryOffset = getRowOffset(range.headerIndex);
28028
+ if (boundaryOffset !== undefined) {
28029
+ const boundaryBottom = boundaryOffset + getRowHeight(range.headerIndex);
28030
+ const slotTop = scrollTop + viewportHeight - cumHeight - rowH;
28031
+ if (boundaryBottom > slotTop) {
28032
+ push = boundaryBottom - slotTop;
28033
+ }
28034
+ }
28035
+ }
28036
+ offsets[i] = push;
28037
+ cumHeight += Math.max(rowH - push, 0);
28038
+ }
28039
+ return { totalHeight: cumHeight, offsets };
28040
+ }
28041
+ /**
28042
+ * @hidden
28043
+ * Applies push transforms to sticky header overlay rows.
28044
+ * Negative push values slide rows up with clipPath.
28045
+ * Iterates each tbody independently so locked and non-locked tables
28046
+ * both receive the same transforms.
28047
+ */
28048
+ function applyHeaderPushTransforms(containerEl, pushOffsets) {
28049
+ const tbodies = containerEl.querySelectorAll('tbody');
28050
+ let maxVisibleHeight = 0;
28051
+ let hasPush = false;
28052
+ for (let t = 0; t < tbodies.length; t++) {
28053
+ const trs = tbodies[t].querySelectorAll(':scope > tr');
28054
+ let totalVisibleHeight = 0;
28055
+ for (let i = 0; i < trs.length && i < pushOffsets.length; i++) {
28056
+ const tr = trs[i];
28057
+ const rowH = tr.offsetHeight || tr.getBoundingClientRect().height;
28058
+ const push = pushOffsets[i];
28059
+ if (push < 0) {
28060
+ hasPush = true;
28061
+ const absPush = -push;
28062
+ const visibleHeight = Math.max(rowH - absPush, 0);
28063
+ totalVisibleHeight += visibleHeight;
28064
+ if (visibleHeight <= 0) {
28065
+ tr.style.display = 'none';
28066
+ tr.style.transform = '';
28067
+ tr.style.clipPath = '';
28068
+ }
28069
+ else {
28070
+ tr.style.display = '';
28071
+ tr.style.transform = `translateY(${push}px)`;
28072
+ tr.style.clipPath = `inset(${absPush}px 0 0 0)`;
28073
+ }
28074
+ }
28075
+ else {
28076
+ totalVisibleHeight += rowH;
28077
+ tr.style.display = '';
28078
+ tr.style.transform = '';
28079
+ tr.style.clipPath = '';
28080
+ }
28081
+ }
28082
+ maxVisibleHeight = Math.max(maxVisibleHeight, totalVisibleHeight);
28083
+ }
28084
+ containerEl.style.height = hasPush ? maxVisibleHeight + 'px' : '';
28085
+ }
28086
+ /**
28087
+ * @hidden
28088
+ * Applies push transforms to sticky footer overlay rows.
28089
+ * Positive push values clip from the bottom. The footer container is bottom-anchored,
28090
+ * so reducing its height shifts content down. Non-pushed rows are translated up to compensate.
28091
+ * Iterates each tbody independently so locked and non-locked tables
28092
+ * both receive the same transforms.
28093
+ */
28094
+ function applyFooterPushTransforms(containerEl, pushOffsets) {
28095
+ const tbodies = containerEl.querySelectorAll('tbody');
28096
+ let maxVisibleHeight = 0;
28097
+ let hasPush = false;
28098
+ for (let t = 0; t < tbodies.length; t++) {
28099
+ const trs = tbodies[t].querySelectorAll(':scope > tr');
28100
+ let totalVisibleHeight = 0;
28101
+ let totalShrink = 0;
28102
+ // First pass: measure heights and compute total shrink
28103
+ const rowHeights = [];
28104
+ for (let i = 0; i < trs.length && i < pushOffsets.length; i++) {
28105
+ const tr = trs[i];
28106
+ const rowH = tr.offsetHeight || tr.getBoundingClientRect().height;
28107
+ rowHeights.push(rowH);
28108
+ const push = pushOffsets[i];
28109
+ if (push > 0) {
28110
+ hasPush = true;
28111
+ const visibleHeight = Math.max(rowH - push, 0);
28112
+ totalVisibleHeight += visibleHeight;
28113
+ totalShrink += rowH - visibleHeight;
28114
+ }
28115
+ else {
28116
+ totalVisibleHeight += rowH;
28117
+ }
28118
+ }
28119
+ // Second pass: apply transforms
28120
+ for (let i = 0; i < trs.length && i < pushOffsets.length; i++) {
28121
+ const tr = trs[i];
28122
+ const push = pushOffsets[i];
28123
+ const rowH = rowHeights[i];
28124
+ if (push > 0) {
28125
+ const visibleHeight = Math.max(rowH - push, 0);
28126
+ if (visibleHeight <= 0) {
28127
+ tr.style.display = '';
28128
+ tr.style.transform = '';
28129
+ tr.style.clipPath = 'inset(0 0 100% 0)';
28130
+ }
28131
+ else {
28132
+ tr.style.display = '';
28133
+ tr.style.transform = '';
28134
+ tr.style.clipPath = `inset(0 0 ${push}px 0)`;
28135
+ }
28136
+ }
28137
+ else {
28138
+ tr.style.display = '';
28139
+ tr.style.transform = hasPush ? `translateY(${-totalShrink}px)` : '';
28140
+ tr.style.clipPath = '';
28141
+ }
28142
+ }
28143
+ maxVisibleHeight = Math.max(maxVisibleHeight, totalVisibleHeight);
28144
+ }
28145
+ containerEl.style.height = hasPush ? maxVisibleHeight + 'px' : '';
28146
+ }
28147
+ /**
28148
+ * @hidden
28149
+ * Resets all push transform styles on rows within a sticky container.
28150
+ */
28151
+ function resetRowTransforms(containerEl) {
28152
+ const trs = containerEl.querySelectorAll('tbody > tr');
28153
+ for (let i = 0; i < trs.length; i++) {
28154
+ const tr = trs[i];
28155
+ tr.style.transform = '';
28156
+ tr.style.clipPath = '';
28157
+ tr.style.display = '';
28158
+ }
28159
+ containerEl.style.height = '';
28160
+ }
28161
+
28162
+ /**
28163
+ * @hidden
28164
+ * Manages sticky group header and footer state for the Grid.
28165
+ * Computes which groups to pin on each scroll frame and applies DOM transforms
28166
+ * for the push effect. Runs outside Angular zone for performance.
28167
+ */
28168
+ class StickyGroupsService {
28169
+ ngZone;
28170
+ headerItems$ = new Subject();
28171
+ footerItems$ = new Subject();
28172
+ scrollToFooter$ = new Subject();
28173
+ scrollToRow$ = new Subject();
28174
+ scrollAndToggle$ = new Subject();
28175
+ stickyHeaderItems = [];
28176
+ stickyFooterItems = [];
28177
+ pendingScrollToFooter = null;
28178
+ /**
28179
+ * When set, after the scroll-to-group completes and the view settles,
28180
+ * the sticky container should focus the real grid row for this group.
28181
+ */
28182
+ pendingFocusGroupIndex = null;
28183
+ focusReady$ = new Subject();
28184
+ /**
28185
+ * When set, the real grid body should scroll until the target row is
28186
+ * rendered, then focus the corresponding cell.
28187
+ */
28188
+ pendingFocusRow = null;
28189
+ /**
28190
+ * When true, the next update() call is skipped. Used when programmatically
28191
+ * scrolling to top so stale DOM measurements don't re-show the overlay.
28192
+ */
28193
+ suppressNextUpdate = false;
28194
+ headerPushOffsets = [];
28195
+ footerPushOffsets = [];
28196
+ groupRangeMap = null;
28197
+ lastFlatDataRef = null;
28198
+ constructor(ngZone) {
28199
+ this.ngZone = ngZone;
28200
+ }
28201
+ ngOnDestroy() {
28202
+ this.headerItems$.complete();
28203
+ this.footerItems$.complete();
28204
+ this.scrollToFooter$.complete();
28205
+ this.scrollToRow$.complete();
28206
+ this.scrollAndToggle$.complete();
28207
+ this.focusReady$.complete();
28208
+ }
28209
+ /**
28210
+ * Requests the list component to scroll to the group header and then
28211
+ * toggle it. Called from sticky overlay click — uses pre-collapse data.
28212
+ */
28213
+ scrollAndToggle(groupIndex) {
28214
+ this.scrollAndToggle$.next(groupIndex);
28215
+ }
28216
+ /**
28217
+ * Requests the grid to scroll so the real footer row at the given flat index
28218
+ * becomes visible at the bottom of the viewport.
28219
+ */
28220
+ requestScrollToFooter(flatIndex) {
28221
+ this.pendingScrollToFooter = flatIndex;
28222
+ this.scrollToFooter$.next(flatIndex);
28223
+ }
28224
+ /**
28225
+ * Consumes (returns and clears) any pending scroll-to-footer request.
28226
+ */
28227
+ consumeScrollToFooter() {
28228
+ const target = this.pendingScrollToFooter;
28229
+ this.pendingScrollToFooter = null;
28230
+ return target;
28231
+ }
28232
+ /**
28233
+ * Requests the real grid body to render the row at the given flat index,
28234
+ * then focus the corresponding cell.
28235
+ */
28236
+ requestScrollToRow(flatIndex, colIndex) {
28237
+ this.pendingFocusRow = { flatIndex, colIndex };
28238
+ this.scrollToRow$.next();
28239
+ }
28240
+ /**
28241
+ * Consumes and clears the pending scroll-to-row request.
28242
+ */
28243
+ consumeScrollToRow() {
28244
+ const target = this.pendingFocusRow;
28245
+ this.pendingFocusRow = null;
28246
+ return target;
28247
+ }
28248
+ /**
28249
+ * Clears cached group range map so it is rebuilt on next update.
28250
+ */
28251
+ invalidateRangeMap() {
28252
+ this.groupRangeMap = null;
28253
+ this.lastFlatDataRef = null;
28254
+ }
28255
+ /**
28256
+ * Main update cycle. Called on every scroll event, outside Angular zone.
28257
+ * Computes which headers/footers should be sticky, applies push transforms,
28258
+ * and emits changes only when the set of sticky items changes.
28259
+ */
28260
+ update(params) {
28261
+ const { container, tbody, flatData, stickyHeaderEl, stickyFooterEl, enableHeaders, enableFooters, rowHeight, skipOffset, rowHeightService } = params;
28262
+ if (!enableHeaders && !enableFooters) {
28263
+ return;
28264
+ }
28265
+ if (!container || !tbody) {
28266
+ return;
28267
+ }
28268
+ if (this.suppressNextUpdate) {
28269
+ this.suppressNextUpdate = false;
28270
+ return;
28271
+ }
28272
+ // Rebuild range map if flat data reference changed
28273
+ if (flatData !== this.lastFlatDataRef) {
28274
+ this.groupRangeMap = buildGroupRangeMap(flatData);
28275
+ this.lastFlatDataRef = flatData;
28276
+ }
28277
+ const groupRanges = this.groupRangeMap;
28278
+ if (!groupRanges || groupRanges.size === 0) {
28279
+ this.clearAll(stickyHeaderEl, stickyFooterEl, container);
28280
+ return;
28281
+ }
28282
+ const footerRanges = new Map();
28283
+ if (enableFooters) {
28284
+ for (const range of groupRanges.values()) {
28285
+ if (range.footerIndex !== null) {
28286
+ footerRanges.set(range.footerIndex, range);
28287
+ }
28288
+ }
28289
+ }
28290
+ const scrollTop = container.scrollTop;
28291
+ const viewportHeight = container.clientHeight;
28292
+ const rows = tbody.children;
28293
+ if (rows.length === 0) {
28294
+ this.clearAll(stickyHeaderEl, stickyFooterEl, container);
28295
+ return;
28296
+ }
28297
+ const defaultRowHeight = rowHeight || 36;
28298
+ const skip = skipOffset || 0;
28299
+ const rhs = rowHeightService;
28300
+ // Measure DOM row positions
28301
+ const containerRect = container.getBoundingClientRect();
28302
+ const tbodyTop = tbody.getBoundingClientRect().top - containerRect.top + scrollTop;
28303
+ const rowCount = rows.length;
28304
+ const rowTops = new Array(rowCount);
28305
+ const rowHeightsArr = new Array(rowCount);
28306
+ for (let i = 0; i < rowCount; i++) {
28307
+ const row = rows[i];
28308
+ rowTops[i] = tbodyTop + row.offsetTop;
28309
+ rowHeightsArr[i] = row.offsetHeight;
28310
+ }
28311
+ const getRowHeight = (flatIndex) => {
28312
+ const local = flatIndex - skip;
28313
+ if (local >= 0 && local < rowCount) {
28314
+ return rowHeightsArr[local];
28315
+ }
28316
+ // Virtual mode fallback: use RowHeightService for off-screen rows
28317
+ if (rhs && flatIndex >= 0 && flatIndex < flatData.length) {
28318
+ return rhs.height(flatIndex);
28319
+ }
28320
+ return defaultRowHeight;
28321
+ };
28322
+ const getRowOffset = (flatIndex) => {
28323
+ const local = flatIndex - skip;
28324
+ if (local >= 0 && local < rowCount) {
28325
+ return rowTops[local];
28326
+ }
28327
+ // Virtual mode fallback: use RowHeightService for off-screen rows
28328
+ if (rhs && flatIndex >= 0 && flatIndex < flatData.length) {
28329
+ return rhs.offset(flatIndex);
28330
+ }
28331
+ return undefined;
28332
+ };
28333
+ // Binary search: first local index where rowTops[i] >= target
28334
+ const bsFirstGte = (target) => {
28335
+ let lo = 0, hi = rowCount;
28336
+ while (lo < hi) {
28337
+ const mid = (lo + hi) >> 1;
28338
+ if (rowTops[mid] < target) {
28339
+ lo = mid + 1;
28340
+ }
28341
+ else {
28342
+ hi = mid;
28343
+ }
28344
+ }
28345
+ return lo;
28346
+ };
28347
+ const findFirstVisible = (effectiveTop) => {
28348
+ const local = bsFirstGte(effectiveTop);
28349
+ return local < rowCount ? skip + local : skip;
28350
+ };
28351
+ const findLastVisible = (effectiveBottom) => {
28352
+ let lo = 0, hi = rowCount - 1;
28353
+ while (lo <= hi) {
28354
+ const mid = (lo + hi) >> 1;
28355
+ if (rowTops[mid] + rowHeightsArr[mid] <= effectiveBottom) {
28356
+ lo = mid + 1;
28357
+ }
28358
+ else {
28359
+ hi = mid - 1;
28360
+ }
28361
+ }
28362
+ // When all DOM rows fit inside the viewport, off-screen rows
28363
+ // beyond the rendered window may also be "visible". Use RHS
28364
+ // to compute the correct last visible index.
28365
+ if (lo === rowCount && rhs && rowCount > 0) {
28366
+ return Math.min(rhs.index(effectiveBottom), flatData.length - 1);
28367
+ }
28368
+ return hi >= 0 ? skip + hi : flatData.length - 1;
28369
+ };
28370
+ const rawFirstVisibleIndex = Math.min(findFirstVisible(scrollTop), flatData.length - 1);
28371
+ let firstVisibleFlatIndex = rawFirstVisibleIndex;
28372
+ // --- Sticky Headers ---
28373
+ if (enableHeaders && stickyHeaderEl) {
28374
+ let stickyHeaderHeight = 0;
28375
+ let headerPushOffsets = [];
28376
+ let stickyHeaders = [];
28377
+ // Convergence loop: recalculate until both set and height stabilise
28378
+ const seenFirstVisible = new Set();
28379
+ while (true) {
28380
+ firstVisibleFlatIndex = Math.min(findFirstVisible(scrollTop + Math.max(stickyHeaderHeight, 0)), flatData.length - 1);
28381
+ const isHeaderCycle = seenFirstVisible.has(firstVisibleFlatIndex);
28382
+ seenFirstVisible.add(firstVisibleFlatIndex);
28383
+ const lastVisibleFlatIndex = Math.min(findLastVisible(scrollTop + viewportHeight), flatData.length - 1);
28384
+ const result = computeStickyItems(flatData, groupRanges, firstVisibleFlatIndex, lastVisibleFlatIndex, rawFirstVisibleIndex);
28385
+ const headerPush = computeHeaderPush(result.map(s => s.flatIndex), getRowHeight, getRowOffset, scrollTop, groupRanges);
28386
+ const headerConverged = !hasStickyItemsChanged(result, stickyHeaders) &&
28387
+ Math.abs(headerPush.totalHeight - stickyHeaderHeight) < 1;
28388
+ stickyHeaders = result;
28389
+ stickyHeaderHeight = headerPush.totalHeight;
28390
+ headerPushOffsets = headerPush.offsets;
28391
+ if (headerConverged || isHeaderCycle) {
28392
+ break;
28393
+ }
28394
+ }
28395
+ const shouldShow = stickyHeaders.length > 0;
28396
+ const hasChanged = hasStickyItemsChanged(stickyHeaders, this.stickyHeaderItems);
28397
+ if (!shouldShow) {
28398
+ stickyHeaderEl.style.display = 'none';
28399
+ resetRowTransforms(stickyHeaderEl);
28400
+ }
28401
+ else if (!hasChanged) {
28402
+ stickyHeaderEl.style.display = '';
28403
+ if (this.headerPushOffsets.length > 0) {
28404
+ // A re-render is pending — update stored offsets so
28405
+ // ngAfterViewChecked applies fresh values to the new DOM
28406
+ // instead of stale ones from the previous hasChanged call.
28407
+ this.headerPushOffsets = headerPushOffsets;
28408
+ }
28409
+ else {
28410
+ applyHeaderPushTransforms(stickyHeaderEl, headerPushOffsets);
28411
+ }
28412
+ }
28413
+ else {
28414
+ resetRowTransforms(stickyHeaderEl);
28415
+ this.headerPushOffsets = headerPushOffsets;
28416
+ }
28417
+ if (hasChanged) {
28418
+ this.stickyHeaderItems = stickyHeaders;
28419
+ // Emit inside zone so Angular re-renders the overlay
28420
+ this.ngZone.run(() => this.headerItems$.next(stickyHeaders));
28421
+ }
28422
+ }
28423
+ // --- Sticky Footers ---
28424
+ if (enableFooters && stickyFooterEl) {
28425
+ let stickyFooterHeight = 0;
28426
+ let footerPushOffsets = [];
28427
+ let stickyFooters = [];
28428
+ const seenLastVisible = new Set();
28429
+ while (true) {
28430
+ const effectiveLastVisible = Math.min(findLastVisible(scrollTop + viewportHeight - stickyFooterHeight), flatData.length - 1);
28431
+ const isFooterCycle = seenLastVisible.has(effectiveLastVisible);
28432
+ seenLastVisible.add(effectiveLastVisible);
28433
+ const footerResult = computeStickyFooterItems(flatData, groupRanges, firstVisibleFlatIndex, effectiveLastVisible);
28434
+ const footerPush = computeFooterPush(footerResult.map(s => s.flatIndex), getRowHeight, getRowOffset, scrollTop, viewportHeight, footerRanges);
28435
+ const footerConverged = !hasStickyItemsChanged(footerResult, stickyFooters) &&
28436
+ Math.abs(footerPush.totalHeight - stickyFooterHeight) < 1;
28437
+ stickyFooters = footerResult;
28438
+ stickyFooterHeight = footerPush.totalHeight;
28439
+ footerPushOffsets = footerPush.offsets;
28440
+ if (footerConverged || isFooterCycle) {
28441
+ break;
28442
+ }
28443
+ }
28444
+ const shouldShow = stickyFooters.length > 0;
28445
+ const hasChanged = hasStickyItemsChanged(stickyFooters, this.stickyFooterItems);
28446
+ if (!shouldShow) {
28447
+ stickyFooterEl.style.display = 'none';
28448
+ stickyFooterEl.style.marginBlockEnd = '';
28449
+ resetRowTransforms(stickyFooterEl);
28450
+ }
28451
+ else if (!hasChanged) {
28452
+ stickyFooterEl.style.display = '';
28453
+ if (this.footerPushOffsets.length > 0) {
28454
+ // A re-render is pending — update stored offsets so
28455
+ // ngAfterViewChecked applies fresh values to the new DOM.
28456
+ this.footerPushOffsets = footerPushOffsets;
28457
+ }
28458
+ else {
28459
+ applyFooterPushTransforms(stickyFooterEl, footerPushOffsets);
28460
+ }
28461
+ }
28462
+ else {
28463
+ resetRowTransforms(stickyFooterEl);
28464
+ this.footerPushOffsets = footerPushOffsets;
28465
+ }
28466
+ if (hasChanged) {
28467
+ this.stickyFooterItems = stickyFooters;
28468
+ this.ngZone.run(() => this.footerItems$.next(stickyFooters));
28469
+ }
28470
+ // Push footer above horizontal scrollbar when present
28471
+ this.adjustStickyFooterForScrollbar(container, stickyFooterEl);
28472
+ }
28473
+ // Update scroll-padding on the container so scrollIntoView accounts for overlays
28474
+ const headerH = stickyHeaderEl?.offsetHeight || 0;
28475
+ const footerH = stickyFooterEl?.offsetHeight || 0;
28476
+ container.style.scrollPaddingTop = headerH > 0 ? headerH + 'px' : '';
28477
+ container.style.scrollPaddingBottom = footerH > 0 ? footerH + 'px' : '';
28478
+ }
28479
+ /**
28480
+ * Applies pending push transforms after Angular re-renders overlay content.
28481
+ * Called from the overlay component's AfterViewChecked hook.
28482
+ */
28483
+ applyPendingHeaderTransforms(containerEl) {
28484
+ if (this.headerPushOffsets.length > 0) {
28485
+ applyHeaderPushTransforms(containerEl, this.headerPushOffsets);
28486
+ this.headerPushOffsets = [];
28487
+ }
28488
+ }
28489
+ applyPendingFooterTransforms(containerEl) {
28490
+ if (this.footerPushOffsets.length > 0) {
28491
+ applyFooterPushTransforms(containerEl, this.footerPushOffsets);
28492
+ this.footerPushOffsets = [];
28493
+ }
28494
+ }
28495
+ /**
28496
+ * Clears all sticky state and resets overlays.
28497
+ */
28498
+ clear() {
28499
+ if (this.stickyHeaderItems.length > 0) {
28500
+ this.stickyHeaderItems = [];
28501
+ this.ngZone.run(() => this.headerItems$.next([]));
28502
+ }
28503
+ if (this.stickyFooterItems.length > 0) {
28504
+ this.stickyFooterItems = [];
28505
+ this.ngZone.run(() => this.footerItems$.next([]));
28506
+ }
28507
+ this.invalidateRangeMap();
28508
+ }
28509
+ /**
28510
+ * Adjusts the container scrollTop so the focused row is not hidden
28511
+ * behind the sticky header or footer overlays.
28512
+ * Intended to be called only from the keyboard navigation path,
28513
+ * outside Angular zone.
28514
+ */
28515
+ adjustScrollForFocusedCell(containerEl, stickyHeaderEl, stickyFooterEl) {
28516
+ if (!containerEl) {
28517
+ return;
28518
+ }
28519
+ // Use document.activeElement so focus in the locked container is also found
28520
+ const focused = (typeof document !== 'undefined' ? document.activeElement : null);
28521
+ if (!focused) {
28522
+ return;
28523
+ }
28524
+ const focusedRow = focused.closest('tr');
28525
+ if (!focusedRow) {
28526
+ return;
28527
+ }
28528
+ const containerRect = containerEl.getBoundingClientRect();
28529
+ const rowRect = focusedRow.getBoundingClientRect();
28530
+ if (stickyHeaderEl && stickyHeaderEl.offsetHeight > 0 && stickyHeaderEl.style.display !== 'none') {
28531
+ const headerBottom = containerRect.top + stickyHeaderEl.offsetHeight;
28532
+ if (rowRect.top < headerBottom) {
28533
+ containerEl.scrollTop -= (headerBottom - rowRect.top);
28534
+ return;
28535
+ }
28536
+ }
28537
+ if (stickyFooterEl && stickyFooterEl.offsetHeight > 0 && stickyFooterEl.style.display !== 'none') {
28538
+ const footerTop = containerRect.bottom - stickyFooterEl.offsetHeight;
28539
+ if (rowRect.bottom > footerTop) {
28540
+ containerEl.scrollTop += (rowRect.bottom - footerTop);
28541
+ }
28542
+ }
28543
+ }
28544
+ /**
28545
+ * Sets marginBlockEnd on the sticky footer overlay when the container
28546
+ * has a horizontal scrollbar, so the footer does not overlap the scrollbar.
28547
+ */
28548
+ adjustStickyFooterForScrollbar(container, stickyFooterEl) {
28549
+ if (!stickyFooterEl) {
28550
+ return;
28551
+ }
28552
+ const hasHorizontalScrollbar = container.scrollWidth > container.clientWidth;
28553
+ stickyFooterEl.style.marginBlockEnd = hasHorizontalScrollbar
28554
+ ? 'var(--kendo-scrollbar-width, 0px)'
28555
+ : '';
28556
+ }
28557
+ clearAll(headerEl, footerEl, container) {
28558
+ if (headerEl) {
28559
+ headerEl.style.display = 'none';
28560
+ resetRowTransforms(headerEl);
28561
+ }
28562
+ if (footerEl) {
28563
+ footerEl.style.display = 'none';
28564
+ footerEl.style.marginBlockEnd = '';
28565
+ resetRowTransforms(footerEl);
28566
+ }
28567
+ container.style.scrollPaddingTop = '';
28568
+ container.style.scrollPaddingBottom = '';
28569
+ if (this.stickyHeaderItems.length > 0) {
28570
+ this.stickyHeaderItems = [];
28571
+ this.ngZone.run(() => this.headerItems$.next([]));
28572
+ }
28573
+ if (this.stickyFooterItems.length > 0) {
28574
+ this.stickyFooterItems = [];
28575
+ this.ngZone.run(() => this.footerItems$.next([]));
28576
+ }
28577
+ }
28578
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: StickyGroupsService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
28579
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: StickyGroupsService });
28580
+ }
28581
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: StickyGroupsService, decorators: [{
28582
+ type: Injectable
28583
+ }], ctorParameters: () => [{ type: i0.NgZone }] });
28584
+
28585
+ /**
28586
+ * @hidden
28587
+ * Renders the sticky group header or footer overlay container.
28588
+ * When locked columns are present, renders two side-by-side tables
28589
+ * mirroring the grid's locked / non-locked split.
28590
+ */
28591
+ class StickyGroupContainerComponent {
28592
+ stickyGroupsService;
28593
+ cdr;
28594
+ ctx;
28595
+ el;
28596
+ navigationService;
28597
+ groupsService;
28598
+ zone;
28599
+ position = 'top';
28600
+ set columns(value) {
28601
+ this._columns = value;
28602
+ this.footerColumns = this.expandSpanColumns(value);
28603
+ }
28604
+ get columns() { return this._columns; }
28605
+ set lockedColumns(value) {
28606
+ this._lockedColumns = value;
28607
+ this.lockedFooterColumns = this.expandSpanColumns(value);
28608
+ }
28609
+ get lockedColumns() { return this._lockedColumns; }
28610
+ groups = [];
28611
+ detailTemplate;
28612
+ totalColumnsCount = 0;
28613
+ hasGroupHeaderColumn = false;
28614
+ groupHeaderColumns = [];
28615
+ tableWidth;
28616
+ lockedWidth;
28617
+ isLocked = false;
28618
+ hostClass = true;
28619
+ get isBottom() {
28620
+ return this.position === 'bottom';
28621
+ }
28622
+ hostRole = 'none';
28623
+ items = [];
28624
+ footerColumns = [];
28625
+ lockedFooterColumns = [];
28626
+ wrapWidth;
28627
+ _columns = [];
28628
+ _lockedColumns = [];
28629
+ subscription;
28630
+ constructor(stickyGroupsService, cdr, ctx, el, navigationService, groupsService, zone) {
28631
+ this.stickyGroupsService = stickyGroupsService;
28632
+ this.cdr = cdr;
28633
+ this.ctx = ctx;
28634
+ this.el = el;
28635
+ this.navigationService = navigationService;
28636
+ this.groupsService = groupsService;
28637
+ this.zone = zone;
28638
+ }
28639
+ captureClickHandler = null;
28640
+ ngOnInit() {
28641
+ const source$ = this.position === 'top'
28642
+ ? this.stickyGroupsService.headerItems$
28643
+ : this.stickyGroupsService.footerItems$;
28644
+ this.subscription = source$.subscribe(items => {
28645
+ this.items = items;
28646
+ this.el.nativeElement.style.display = items.length === 0 ? 'none' : '';
28647
+ this.cdr.markForCheck();
28648
+ });
28649
+ // Capture-phase listener intercepts <a> clicks before GroupHeaderComponent's
28650
+ // toggleGroup handler fires, so we control the scroll-then-toggle sequence.
28651
+ if (this.position === 'top') {
28652
+ this.captureClickHandler = (e) => this.onCapturedClick(e);
28653
+ this.el.nativeElement.addEventListener('click', this.captureClickHandler, true);
28654
+ }
28655
+ // Subscribe to deferred focus events (used after scroll-to-group in virtual mode)
28656
+ if (this.position === 'top') {
28657
+ this.subscription.add(this.stickyGroupsService.focusReady$.subscribe(({ flatIndex }) => {
28658
+ this.clearStickyFocus();
28659
+ this.focusRealGridRow(flatIndex);
28660
+ }));
28661
+ }
28662
+ }
28663
+ ngAfterViewChecked() {
28664
+ const hostEl = this.el?.nativeElement;
28665
+ if (!hostEl) {
28666
+ return;
28667
+ }
28668
+ // Sync locked/non-locked row heights BEFORE applying transforms,
28669
+ // so transform calculations use consistent row measurements.
28670
+ if (this.isLocked && this.items.length > 0) {
28671
+ this.wrapWidth = Math.max(hostEl.clientWidth - this.lockedWidth, 0);
28672
+ this.syncLockedRowHeights();
28673
+ }
28674
+ if (this.position === 'top') {
28675
+ this.stickyGroupsService.applyPendingHeaderTransforms(hostEl);
28676
+ }
28677
+ else {
28678
+ this.stickyGroupsService.applyPendingFooterTransforms(hostEl);
28679
+ }
28680
+ }
28681
+ ngOnDestroy() {
28682
+ this.subscription?.unsubscribe();
28683
+ if (this.captureClickHandler) {
28684
+ this.el.nativeElement.removeEventListener('click', this.captureClickHandler, true);
28685
+ this.captureClickHandler = null;
28686
+ }
28687
+ }
28688
+ get isStacked() {
28689
+ return this.ctx.grid?.isStacked;
28690
+ }
28691
+ get gridSize() {
28692
+ return this.ctx.grid?.size;
28693
+ }
28694
+ get nativeElement() {
28695
+ return this.el?.nativeElement;
28696
+ }
28697
+ /**
28698
+ * Handles clicks on the sticky container.
28699
+ * For headers: toggle (anchor) clicks scroll grid to real header position;
28700
+ * non-toggle clicks focus the clicked row.
28701
+ * For footers: focus the clicked cell.
28702
+ */
28703
+ onContainerClick(event) {
28704
+ const target = event.target;
28705
+ const row = target.closest('tr');
28706
+ if (!row) {
28707
+ return;
28708
+ }
28709
+ if (this.position === 'bottom') {
28710
+ const td = target.closest('td[data-col-index]');
28711
+ if (td) {
28712
+ this.focusFooterCell(td);
28713
+ }
28714
+ return;
28715
+ }
28716
+ // Anchor clicks are handled by onStickyHeaderRowClick (template handler)
28717
+ // which stops propagation. If we still get here, just focus the row.
28718
+ if (row) {
28719
+ this.focusStickyCell(row);
28720
+ }
28721
+ }
28722
+ // Prevent focusin from bubbling to the grid wrapper where NavigationService
28723
+ // would misinterpret the sticky row's aria-rowindex as a real grid row.
28724
+ onContainerFocusIn(event) {
28725
+ event.stopPropagation();
28726
+ }
28727
+ onContainerFocusOut(event) {
28728
+ const related = event.relatedTarget;
28729
+ if (!related || !this.el.nativeElement.contains(related)) {
28730
+ this.clearStickyFocus();
28731
+ }
28732
+ }
28733
+ onContainerKeydown(event) {
28734
+ const code = normalizeKeys(event);
28735
+ if (this.position === 'bottom') {
28736
+ this.handleFooterKeydown(event, code);
28737
+ return;
28738
+ }
28739
+ if (code !== Keys.ArrowUp && code !== Keys.ArrowDown && code !== Keys.Enter) {
28740
+ return;
28741
+ }
28742
+ const focusedIndex = this.getFocusedItemIndex();
28743
+ if (focusedIndex < 0) {
28744
+ return;
28745
+ }
28746
+ event.preventDefault();
28747
+ event.stopPropagation();
28748
+ if (code === Keys.Enter) {
28749
+ this.handleEnterKey(focusedIndex);
28750
+ }
28751
+ else if (code === Keys.ArrowUp) {
28752
+ this.handleArrowUp(focusedIndex);
28753
+ }
28754
+ else if (code === Keys.ArrowDown) {
28755
+ this.handleArrowDown(focusedIndex);
28756
+ }
28757
+ }
28758
+ expandSpanColumns(cols) {
28759
+ return Array.from(cols).reduce((result, col) => {
28760
+ const newCols = (col instanceof SpanColumnComponent) ? Array.from(col.children) : [col];
28761
+ return [...result, ...newCols];
28762
+ }, []);
28763
+ }
28764
+ getFocusedItemIndex() {
28765
+ if (!isDocumentAvailable()) {
28766
+ return -1;
28767
+ }
28768
+ const active = document.activeElement;
28769
+ if (!active || !this.el.nativeElement.contains(active)) {
28770
+ return -1;
28771
+ }
28772
+ const row = active.tagName === 'TR' ? active : active.closest('tr');
28773
+ if (!row) {
28774
+ return -1;
28775
+ }
28776
+ const groupIndex = row.getAttribute('data-group-index');
28777
+ return this.items.findIndex(item => item.item.index === groupIndex);
28778
+ }
28779
+ handleArrowUp(currentIndex) {
28780
+ if (currentIndex > 0) {
28781
+ this.focusStickyRowByIndex(currentIndex - 1);
28782
+ }
28783
+ else {
28784
+ // Exit the sticky overlay upward.
28785
+ // The overlay is visible because the real group header scrolled out of view,
28786
+ // so focusing the row above it (flatIndex - 1) would land on a non-visible row.
28787
+ // Navigate to pinned-top rows or the grid header instead.
28788
+ const meta = this.navigationService.metadata;
28789
+ if (meta && meta.pinnedTopRowsCount > 0) {
28790
+ this.clearStickyFocus();
28791
+ // meta.headerRows includes the +1 offset already accounted for in
28792
+ // logicalRowIndex, so the last pinned-top row is at:
28793
+ // (meta.headerRows - 1) + pinnedTopRowsCount
28794
+ const lastPinnedTopRow = meta.headerRows - 1 + meta.pinnedTopRowsCount;
28795
+ const firstCol = meta.hasDetailTemplate && !meta.isStacked ? 1 : 0;
28796
+ this.navigationService.focusCell(lastPinnedTopRow, firstCol);
28797
+ }
28798
+ else {
28799
+ // No pinned-top rows — go to grid header.
28800
+ // Explicitly hide the sticky container and scroll to top before focusing,
28801
+ // so the header area is fully visible.
28802
+ this.clearStickyFocus();
28803
+ this.items = [];
28804
+ this.el.nativeElement.style.display = 'none';
28805
+ this.cdr.markForCheck();
28806
+ if (meta) {
28807
+ const gridContainer = this.el.nativeElement.closest('.k-grid-container');
28808
+ const scrollContainer = gridContainer
28809
+ ?.querySelector('.k-grid-content.k-virtual-content');
28810
+ if (scrollContainer) {
28811
+ this.stickyGroupsService.suppressNextUpdate = true;
28812
+ scrollContainer.scrollTop = 0;
28813
+ }
28814
+ this.zone.runOutsideAngular(() => {
28815
+ setTimeout(() => {
28816
+ this.navigationService.focusCell(meta.headerRows - 1, 0);
28817
+ });
28818
+ });
28819
+ }
28820
+ }
28821
+ }
28822
+ }
28823
+ handleArrowDown(currentIndex) {
28824
+ if (currentIndex < this.items.length - 1) {
28825
+ this.focusStickyRowByIndex(currentIndex + 1);
28826
+ }
28827
+ else {
28828
+ // Exit container downward: focus first child of the group
28829
+ const flatIndex = this.items[currentIndex].flatIndex;
28830
+ this.focusRealGridRow(flatIndex + 1);
28831
+ }
28832
+ }
28833
+ handleEnterKey(currentIndex) {
28834
+ const stickyItem = this.items[currentIndex];
28835
+ const groupLevel = stickyItem.item.level;
28836
+ this.scrollToGroupAndToggle(stickyItem, groupLevel);
28837
+ }
28838
+ /**
28839
+ * Capture-phase click handler registered on the host element.
28840
+ * Intercepts <a> clicks inside sticky header rows before
28841
+ * GroupHeaderComponent's toggleGroup fires.
28842
+ */
28843
+ onCapturedClick(event) {
28844
+ const target = event.target;
28845
+ const anchor = target.closest('a');
28846
+ if (!anchor) {
28847
+ return;
28848
+ }
28849
+ const row = anchor.closest('tr.k-grouping-row');
28850
+ if (!row) {
28851
+ return;
28852
+ }
28853
+ const groupIndex = row.getAttribute('data-group-index');
28854
+ if (!groupIndex) {
28855
+ return;
28856
+ }
28857
+ // Stop the event so GroupHeaderComponent.toggleGroup never fires
28858
+ event.stopPropagation();
28859
+ event.preventDefault();
28860
+ const stickyItem = this.items.find(item => item.item.index === groupIndex);
28861
+ if (stickyItem) {
28862
+ const groupLevel = stickyItem.item.level;
28863
+ this.scrollToGroupAndToggle(stickyItem, groupLevel);
28864
+ }
28865
+ }
28866
+ /**
28867
+ * Triggers the scroll-to-root-and-toggle flow via the service.
28868
+ * The list component handles actual scrolling, toggling, and focus.
28869
+ */
28870
+ scrollToGroupAndToggle(stickyItem, groupLevel) {
28871
+ const groupItem = stickyItem.item;
28872
+ const groupIndex = groupItem.index;
28873
+ this.stickyGroupsService.pendingFocusGroupIndex = groupIndex;
28874
+ this.stickyGroupsService.scrollAndToggle(groupIndex);
28875
+ }
28876
+ focusStickyRowByIndex(itemIndex) {
28877
+ const hostEl = this.el.nativeElement;
28878
+ if (!hostEl) {
28879
+ return;
28880
+ }
28881
+ const groupIndex = this.items[itemIndex].item.index;
28882
+ const targetRow = hostEl.querySelector(`tr[data-group-index="${groupIndex}"]`);
28883
+ if (targetRow) {
28884
+ this.focusStickyCell(targetRow);
28885
+ }
28886
+ }
28887
+ focusStickyCell(row) {
28888
+ const hostEl = this.el.nativeElement;
28889
+ const groupIndex = row.getAttribute('data-group-index');
28890
+ // Clear grid-level focus so there's no double highlight
28891
+ this.clearGridFocus();
28892
+ this.clearStickyFocus();
28893
+ // In locked mode, always focus the TD in the locked table (contains the toggle)
28894
+ // and visually highlight both locked and non-locked TDs.
28895
+ if (this.isLocked && groupIndex) {
28896
+ const lockedRow = hostEl.querySelector(`.k-grid-content-locked tr[data-group-index="${groupIndex}"]`);
28897
+ const nonLockedRow = hostEl.querySelector(`:scope > .k-grid-header-wrap tr[data-group-index="${groupIndex}"], :scope > .k-grid-footer-wrap tr[data-group-index="${groupIndex}"]`);
28898
+ const lockedTd = lockedRow?.querySelector('td[kendogridlogicalcell]');
28899
+ const nonLockedTd = nonLockedRow?.querySelector('td');
28900
+ if (lockedTd) {
28901
+ if (!lockedTd.hasAttribute('tabindex')) {
28902
+ lockedTd.setAttribute('tabindex', '-1');
28903
+ }
28904
+ lockedTd.classList.add('k-focus');
28905
+ lockedTd.focus();
28906
+ }
28907
+ if (nonLockedTd) {
28908
+ nonLockedTd.classList.add('k-focus');
28909
+ }
28910
+ }
28911
+ else {
28912
+ const td = row.querySelector('td[kendogridlogicalcell]');
28913
+ if (td) {
28914
+ if (!td.hasAttribute('tabindex')) {
28915
+ td.setAttribute('tabindex', '-1');
28916
+ }
28917
+ td.classList.add('k-focus');
28918
+ td.focus();
28919
+ }
28920
+ }
28921
+ }
28922
+ clearStickyFocus() {
28923
+ const hostEl = this.el.nativeElement;
28924
+ hostEl.querySelectorAll('.k-focus').forEach((el) => el.classList.remove('k-focus'));
28925
+ }
28926
+ /**
28927
+ * Clears k-focus from all grid content cells (outside any sticky container)
28928
+ * so that the regular grid and the sticky overlay don't show focus simultaneously.
28929
+ */
28930
+ clearGridFocus() {
28931
+ const ariaRoot = this.el.nativeElement.closest('.k-grid-aria-root');
28932
+ if (!ariaRoot) {
28933
+ return;
28934
+ }
28935
+ ariaRoot.querySelectorAll('.k-focus').forEach((el) => {
28936
+ if (!el.closest('.k-grid-sticky-container')) {
28937
+ el.classList.remove('k-focus');
28938
+ }
28939
+ });
28940
+ }
28941
+ focusRealGridRow(flatIndex, colIndex = 0) {
28942
+ this.stickyGroupsService.requestScrollToRow(flatIndex, colIndex);
28943
+ }
28944
+ handleFooterKeydown(event, code) {
28945
+ if (code !== Keys.ArrowUp && code !== Keys.ArrowDown &&
28946
+ code !== Keys.ArrowLeft && code !== Keys.ArrowRight) {
28947
+ return;
28948
+ }
28949
+ if (!isDocumentAvailable()) {
28950
+ return;
28951
+ }
28952
+ const active = document.activeElement;
28953
+ if (!active || !this.el.nativeElement.contains(active)) {
28954
+ return;
28955
+ }
28956
+ const td = active.closest('td[data-col-index]');
28957
+ if (!td) {
28958
+ return;
28959
+ }
28960
+ const row = td.closest('tr[data-sticky-row]');
28961
+ if (!row) {
28962
+ return;
28963
+ }
28964
+ const rowIdx = parseInt(row.getAttribute('data-sticky-row'), 10);
28965
+ const colIdx = parseInt(td.getAttribute('data-col-index'), 10);
28966
+ event.preventDefault();
28967
+ event.stopPropagation();
28968
+ if (code === Keys.ArrowLeft) {
28969
+ this.focusFooterCellAt(rowIdx, colIdx, -1);
28970
+ }
28971
+ else if (code === Keys.ArrowRight) {
28972
+ this.focusFooterCellAt(rowIdx, colIdx, 1);
28973
+ }
28974
+ else if (code === Keys.ArrowUp) {
28975
+ if (rowIdx > 0) {
28976
+ this.focusFooterCellInRow(rowIdx - 1, colIdx);
28977
+ }
28978
+ else {
28979
+ // Exit upward: focus the last child row before the topmost footer
28980
+ const topFooter = this.items[0];
28981
+ const targetFlatIndex = topFooter.flatIndex - 1;
28982
+ if (targetFlatIndex >= 0) {
28983
+ this.clearStickyFocus();
28984
+ this.focusRealGridRow(targetFlatIndex, colIdx);
28985
+ }
28986
+ }
28987
+ }
28988
+ else if (code === Keys.ArrowDown) {
28989
+ if (rowIdx < this.items.length - 1) {
28990
+ this.focusFooterCellInRow(rowIdx + 1, colIdx);
28991
+ }
28992
+ else {
28993
+ // Exit downward from the last sticky footer row.
28994
+ // If pinned-bottom rows are present, navigate to the first pinned-bottom row.
28995
+ const meta = this.navigationService.metadata;
28996
+ if (meta && meta.pinnedBottomRowsCount > 0) {
28997
+ // logicalRowIndex for the first pinned-bottom row (rowIndex=0):
28998
+ // (meta.headerRows - 1) + pinnedTopCount + dataRows + 1
28999
+ // = meta.headerRows + pinnedTopCount + dataRows
29000
+ const firstPinnedBottomRow = meta.headerRows + meta.pinnedTopRowsCount + meta.dataRows;
29001
+ this.clearStickyFocus();
29002
+ this.navigationService.focusCell(firstPinnedBottomRow, colIdx);
29003
+ }
29004
+ else {
29005
+ // No pinned-bottom rows — go to next data row or grid footer
29006
+ const bottomFooter = this.items[this.items.length - 1];
29007
+ const targetFlatIndex = bottomFooter.flatIndex + 1;
29008
+ if (meta && targetFlatIndex < meta.dataRows) {
29009
+ this.clearStickyFocus();
29010
+ this.focusRealGridRow(targetFlatIndex, colIdx);
29011
+ }
29012
+ else if (meta && meta.footerRow > 0) {
29013
+ // Last footer in the data — go to grid footer if present
29014
+ this.clearStickyFocus();
29015
+ this.navigationService.focusCell(meta.maxLogicalRowIndex, colIdx);
29016
+ }
29017
+ }
29018
+ }
29019
+ }
29020
+ }
29021
+ focusFooterCell(td) {
29022
+ // Clear grid-level focus so there's no double highlight
29023
+ this.clearGridFocus();
29024
+ this.clearStickyFocus();
29025
+ if (!td.hasAttribute('tabindex')) {
29026
+ td.setAttribute('tabindex', '-1');
29027
+ }
29028
+ td.classList.add('k-focus');
29029
+ td.focus();
29030
+ }
29031
+ /**
29032
+ * Moves focus to the adjacent cell in the given direction (-1 = left, +1 = right).
29033
+ * Crosses the locked/non-locked boundary seamlessly.
29034
+ */
29035
+ focusFooterCellAt(rowIdx, currentColIdx, direction) {
29036
+ const allCells = this.getFooterRowCells(rowIdx);
29037
+ const currentPos = allCells.findIndex(c => parseInt(c.getAttribute('data-col-index'), 10) === currentColIdx);
29038
+ const nextPos = currentPos + direction;
29039
+ if (nextPos >= 0 && nextPos < allCells.length) {
29040
+ this.focusFooterCell(allCells[nextPos]);
29041
+ }
29042
+ }
29043
+ /**
29044
+ * Focuses the cell with the given column index in a different row.
29045
+ */
29046
+ focusFooterCellInRow(rowIdx, colIdx) {
29047
+ const allCells = this.getFooterRowCells(rowIdx);
29048
+ const target = allCells.find(c => parseInt(c.getAttribute('data-col-index'), 10) === colIdx)
29049
+ || allCells[allCells.length - 1];
29050
+ if (target) {
29051
+ this.focusFooterCell(target);
29052
+ }
29053
+ }
29054
+ /**
29055
+ * Returns all data cells (with data-col-index) for a footer row,
29056
+ * combining locked and non-locked tables, sorted by column index.
29057
+ */
29058
+ getFooterRowCells(rowIdx) {
29059
+ const hostEl = this.el.nativeElement;
29060
+ const cells = Array.from(hostEl.querySelectorAll(`tr[data-sticky-row="${rowIdx}"] td[data-col-index]`));
29061
+ cells.sort((a, b) => parseInt(a.getAttribute('data-col-index'), 10) -
29062
+ parseInt(b.getAttribute('data-col-index'), 10));
29063
+ return cells;
29064
+ }
29065
+ syncLockedRowHeights() {
29066
+ const hostEl = this.el?.nativeElement;
29067
+ if (!hostEl) {
29068
+ return;
29069
+ }
29070
+ const lockedTable = hostEl.querySelector('.k-grid-content-locked table');
29071
+ const wrapSelector = this.position === 'top' ? '.k-grid-header-wrap' : '.k-grid-footer-wrap';
29072
+ const scrollableTable = hostEl.querySelector(`${wrapSelector} table`);
29073
+ if (lockedTable && scrollableTable) {
29074
+ syncRowsHeight(lockedTable, scrollableTable);
29075
+ }
29076
+ }
29077
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: StickyGroupContainerComponent, deps: [{ token: StickyGroupsService }, { token: i0.ChangeDetectorRef }, { token: ContextService }, { token: i0.ElementRef }, { token: NavigationService }, { token: GroupsService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
29078
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: StickyGroupContainerComponent, isStandalone: true, selector: "[kendoGridStickyGroupContainer]", inputs: { position: "position", columns: "columns", lockedColumns: "lockedColumns", groups: "groups", detailTemplate: "detailTemplate", totalColumnsCount: "totalColumnsCount", hasGroupHeaderColumn: "hasGroupHeaderColumn", groupHeaderColumns: "groupHeaderColumns", tableWidth: "tableWidth", lockedWidth: "lockedWidth", isLocked: "isLocked" }, host: { listeners: { "click": "onContainerClick($event)", "focusin": "onContainerFocusIn($event)", "focusout": "onContainerFocusOut($event)", "keydown": "onContainerKeydown($event)" }, properties: { "class.k-grid-sticky-container": "this.hostClass", "class.k-pos-bottom": "this.isBottom", "attr.role": "this.hostRole" } }, providers: [
29079
+ { provide: SKIP_CELL_NAVIGATION, useValue: true }
29080
+ ], ngImport: i0, template: `
29081
+ @if (isLocked) {
29082
+ <div class="k-grid-content-locked" role="presentation" [style.width.px]="lockedWidth">
29083
+ <table
29084
+ class="k-grid-table k-table"
29085
+ [class.k-grid-group-sticky-header-table]="position === 'top'"
29086
+ [class.k-grid-group-sticky-footer-table]="position === 'bottom'"
29087
+ [class.k-table-sm]="gridSize === 'small'"
29088
+ [class.k-table-md]="gridSize === 'medium'"
29089
+ role="none"
29090
+ [style.width.px]="lockedWidth">
29091
+ <colgroup kendoGridColGroup
29092
+ [groups]="groups"
29093
+ [columns]="$any(lockedColumns)"
29094
+ [detailTemplate]="detailTemplate">
29095
+ </colgroup>
29096
+ <tbody class="k-table-tbody" role="presentation">
29097
+ @if (position === 'top') {
29098
+ @for (stickyItem of items; track stickyItem.flatIndex) {
29099
+ <tr role="row"
29100
+ class="k-grouping-row k-table-group-row k-table-row"
29101
+ kendoGridGroupHeader
29102
+ [columns]="lockedColumns"
29103
+ [groups]="groups"
29104
+ [item]="$any(stickyItem.item)"
29105
+ [hasDetails]="!!detailTemplate?.templateRef"
29106
+ [skipGroupDecoration]="false"
29107
+ [hasGroupHeaderColumn]="false"
29108
+ [groupHeaderColumns]="[]"
29109
+ [rowIndex]="stickyItem.flatIndex + 1"
29110
+ [totalColumnsCount]="totalColumnsCount"
29111
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29112
+ [attr.data-group-index]="$any(stickyItem.item).index"
29113
+ [attr.data-group-level]="$any(stickyItem.item).level"
29114
+ tabindex="-1">
29115
+ </tr>
29116
+ }
29117
+ }
29118
+ @if (position === 'bottom') {
29119
+ @for (stickyItem of items; track stickyItem.flatIndex; let rowIdx = $index) {
29120
+ <tr role="row"
29121
+ class="k-group-footer"
29122
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29123
+ [attr.data-sticky-row]="rowIdx">
29124
+ @for (g of groups; track $index) {
29125
+ <td class="k-group-cell k-table-td k-table-group-td" role="presentation"></td>
29126
+ }
29127
+ @if (detailTemplate?.templateRef) {
29128
+ <td class="k-hierarchy-cell k-table-td" role="gridcell"
29129
+ aria-selected="false">
29130
+ </td>
29131
+ }
29132
+ @for (column of lockedFooterColumns; track $index; let columnIndex = $index) {
29133
+ <td role="gridcell"
29134
+ class="k-table-td"
29135
+ tabindex="-1"
29136
+ [attr.aria-colindex]="columnIndex + 1"
29137
+ [attr.data-col-index]="$any(column).leafIndex">
29138
+ <ng-template
29139
+ [templateContext]="{
29140
+ templateRef: $any(column).groupFooterTemplateRef,
29141
+ group: $any(stickyItem.item).data,
29142
+ field: $any(column).field,
29143
+ column: column,
29144
+ aggregates: $any(stickyItem.item).data?.aggregates,
29145
+ $implicit: $any(stickyItem.item).data?.aggregates
29146
+ }">
29147
+ </ng-template>
29148
+ </td>
29149
+ }
29150
+ </tr>
29151
+ }
29152
+ }
29153
+ </tbody>
29154
+ </table>
29155
+ </div>
29156
+ }
29157
+ <div [class.k-grid-header-wrap]="position === 'top'" [class.k-grid-footer-wrap]="position === 'bottom'" role="presentation"
29158
+ [style.width.px]="isLocked ? wrapWidth : null">
29159
+ <table
29160
+ class="k-grid-table k-table"
29161
+ [class.k-grid-group-sticky-header-table]="position === 'top'"
29162
+ [class.k-grid-group-sticky-footer-table]="position === 'bottom'"
29163
+ [class.k-table-sm]="gridSize === 'small'"
29164
+ [class.k-table-md]="gridSize === 'medium'"
29165
+ role="none"
29166
+ [style.width.px]="tableWidth">
29167
+ <colgroup kendoGridColGroup
29168
+ [groups]="isLocked ? [] : groups"
29169
+ [columns]="$any(columns)"
29170
+ [detailTemplate]="isLocked ? null : detailTemplate">
29171
+ </colgroup>
29172
+ <tbody class="k-table-tbody" role="rowgroup">
29173
+ @if (position === 'top') {
29174
+ @for (stickyItem of items; track stickyItem.flatIndex) {
29175
+ <tr role="row"
29176
+ class="k-grouping-row k-table-group-row k-table-row"
29177
+ kendoGridGroupHeader
29178
+ [columns]="columns"
29179
+ [groups]="groups"
29180
+ [item]="$any(stickyItem.item)"
29181
+ [hasDetails]="!!detailTemplate?.templateRef"
29182
+ [skipGroupDecoration]="isLocked"
29183
+ [hasGroupHeaderColumn]="hasGroupHeaderColumn"
29184
+ [groupHeaderColumns]="groupHeaderColumns"
29185
+ [rowIndex]="stickyItem.flatIndex + 1"
29186
+ [totalColumnsCount]="totalColumnsCount"
29187
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29188
+ [attr.data-group-index]="$any(stickyItem.item).index"
29189
+ [attr.data-group-level]="$any(stickyItem.item).level"
29190
+ tabindex="-1">
29191
+ </tr>
29192
+ }
29193
+ }
29194
+ @if (position === 'bottom') {
29195
+ @for (stickyItem of items; track stickyItem.flatIndex; let rowIdx = $index) {
29196
+ <tr role="row"
29197
+ class="k-group-footer"
29198
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29199
+ [attr.data-sticky-row]="rowIdx">
29200
+ @if (!isLocked) {
29201
+ @for (g of groups; track $index) {
29202
+ <td class="k-group-cell k-table-td k-table-group-td" role="presentation"></td>
29203
+ }
29204
+ @if (detailTemplate?.templateRef && !isStacked) {
29205
+ <td class="k-hierarchy-cell k-table-td" role="gridcell"
29206
+ aria-selected="false">
29207
+ </td>
29208
+ }
29209
+ }
29210
+ @if (!isStacked) {
29211
+ @for (column of footerColumns; track $index; let columnIndex = $index) {
29212
+ <td role="gridcell"
29213
+ class="k-table-td"
29214
+ tabindex="-1"
29215
+ [attr.aria-colindex]="columnIndex + 1"
29216
+ [attr.data-col-index]="$any(column).leafIndex">
29217
+ <ng-template
29218
+ [templateContext]="{
29219
+ templateRef: $any(column).groupFooterTemplateRef,
29220
+ group: $any(stickyItem.item).data,
29221
+ field: $any(column).field,
29222
+ column: column,
29223
+ aggregates: $any(stickyItem.item).data?.aggregates,
29224
+ $implicit: $any(stickyItem.item).data?.aggregates
29225
+ }">
29226
+ </ng-template>
29227
+ </td>
29228
+ }
29229
+ } @else {
29230
+ <td role="gridcell" class="k-table-td">
29231
+ <div class="k-grid-column-template">
29232
+ @for (col of footerColumns; track $index) {
29233
+ @if ($any(col).groupFooterTemplateRef) {
29234
+ <div class="k-column-template-item">
29235
+ <ng-template
29236
+ [templateContext]="{
29237
+ templateRef: $any(col).groupFooterTemplateRef,
29238
+ group: $any(stickyItem.item).data,
29239
+ field: $any(col).field,
29240
+ column: col,
29241
+ aggregates: $any(stickyItem.item).data?.aggregates,
29242
+ $implicit: $any(stickyItem.item).data?.aggregates
29243
+ }">
29244
+ </ng-template>
29245
+ </div>
29246
+ }
29247
+ }
29248
+ </div>
29249
+ </td>
29250
+ }
29251
+ </tr>
29252
+ }
29253
+ }
29254
+ </tbody>
29255
+ </table>
29256
+ </div>
29257
+ `, isInline: true, dependencies: [{ kind: "component", type: ColGroupComponent, selector: "[kendoGridColGroup]", inputs: ["columns", "groups", "detailTemplate", "sort"] }, { kind: "component", type: GroupHeaderComponent, selector: "[kendoGridGroupHeader]", inputs: ["rowIndex", "logicalRowIndex", "item", "skipGroupDecoration", "hasDetails", "totalColumnsCount", "hasGroupHeaderColumn", "groupHeaderColumns", "columns", "groups"] }, { kind: "directive", type: TemplateContextDirective, selector: "[templateContext]", inputs: ["templateContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29258
+ }
29259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: StickyGroupContainerComponent, decorators: [{
29260
+ type: Component,
29261
+ args: [{
29262
+ selector: '[kendoGridStickyGroupContainer]',
29263
+ changeDetection: ChangeDetectionStrategy.OnPush,
29264
+ template: `
29265
+ @if (isLocked) {
29266
+ <div class="k-grid-content-locked" role="presentation" [style.width.px]="lockedWidth">
29267
+ <table
29268
+ class="k-grid-table k-table"
29269
+ [class.k-grid-group-sticky-header-table]="position === 'top'"
29270
+ [class.k-grid-group-sticky-footer-table]="position === 'bottom'"
29271
+ [class.k-table-sm]="gridSize === 'small'"
29272
+ [class.k-table-md]="gridSize === 'medium'"
29273
+ role="none"
29274
+ [style.width.px]="lockedWidth">
29275
+ <colgroup kendoGridColGroup
29276
+ [groups]="groups"
29277
+ [columns]="$any(lockedColumns)"
29278
+ [detailTemplate]="detailTemplate">
29279
+ </colgroup>
29280
+ <tbody class="k-table-tbody" role="presentation">
29281
+ @if (position === 'top') {
29282
+ @for (stickyItem of items; track stickyItem.flatIndex) {
29283
+ <tr role="row"
29284
+ class="k-grouping-row k-table-group-row k-table-row"
29285
+ kendoGridGroupHeader
29286
+ [columns]="lockedColumns"
29287
+ [groups]="groups"
29288
+ [item]="$any(stickyItem.item)"
29289
+ [hasDetails]="!!detailTemplate?.templateRef"
29290
+ [skipGroupDecoration]="false"
29291
+ [hasGroupHeaderColumn]="false"
29292
+ [groupHeaderColumns]="[]"
29293
+ [rowIndex]="stickyItem.flatIndex + 1"
29294
+ [totalColumnsCount]="totalColumnsCount"
29295
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29296
+ [attr.data-group-index]="$any(stickyItem.item).index"
29297
+ [attr.data-group-level]="$any(stickyItem.item).level"
29298
+ tabindex="-1">
29299
+ </tr>
29300
+ }
29301
+ }
29302
+ @if (position === 'bottom') {
29303
+ @for (stickyItem of items; track stickyItem.flatIndex; let rowIdx = $index) {
29304
+ <tr role="row"
29305
+ class="k-group-footer"
29306
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29307
+ [attr.data-sticky-row]="rowIdx">
29308
+ @for (g of groups; track $index) {
29309
+ <td class="k-group-cell k-table-td k-table-group-td" role="presentation"></td>
29310
+ }
29311
+ @if (detailTemplate?.templateRef) {
29312
+ <td class="k-hierarchy-cell k-table-td" role="gridcell"
29313
+ aria-selected="false">
29314
+ </td>
29315
+ }
29316
+ @for (column of lockedFooterColumns; track $index; let columnIndex = $index) {
29317
+ <td role="gridcell"
29318
+ class="k-table-td"
29319
+ tabindex="-1"
29320
+ [attr.aria-colindex]="columnIndex + 1"
29321
+ [attr.data-col-index]="$any(column).leafIndex">
29322
+ <ng-template
29323
+ [templateContext]="{
29324
+ templateRef: $any(column).groupFooterTemplateRef,
29325
+ group: $any(stickyItem.item).data,
29326
+ field: $any(column).field,
29327
+ column: column,
29328
+ aggregates: $any(stickyItem.item).data?.aggregates,
29329
+ $implicit: $any(stickyItem.item).data?.aggregates
29330
+ }">
29331
+ </ng-template>
29332
+ </td>
29333
+ }
29334
+ </tr>
29335
+ }
29336
+ }
29337
+ </tbody>
29338
+ </table>
29339
+ </div>
29340
+ }
29341
+ <div [class.k-grid-header-wrap]="position === 'top'" [class.k-grid-footer-wrap]="position === 'bottom'" role="presentation"
29342
+ [style.width.px]="isLocked ? wrapWidth : null">
29343
+ <table
29344
+ class="k-grid-table k-table"
29345
+ [class.k-grid-group-sticky-header-table]="position === 'top'"
29346
+ [class.k-grid-group-sticky-footer-table]="position === 'bottom'"
29347
+ [class.k-table-sm]="gridSize === 'small'"
29348
+ [class.k-table-md]="gridSize === 'medium'"
29349
+ role="none"
29350
+ [style.width.px]="tableWidth">
29351
+ <colgroup kendoGridColGroup
29352
+ [groups]="isLocked ? [] : groups"
29353
+ [columns]="$any(columns)"
29354
+ [detailTemplate]="isLocked ? null : detailTemplate">
29355
+ </colgroup>
29356
+ <tbody class="k-table-tbody" role="rowgroup">
29357
+ @if (position === 'top') {
29358
+ @for (stickyItem of items; track stickyItem.flatIndex) {
29359
+ <tr role="row"
29360
+ class="k-grouping-row k-table-group-row k-table-row"
29361
+ kendoGridGroupHeader
29362
+ [columns]="columns"
29363
+ [groups]="groups"
29364
+ [item]="$any(stickyItem.item)"
29365
+ [hasDetails]="!!detailTemplate?.templateRef"
29366
+ [skipGroupDecoration]="isLocked"
29367
+ [hasGroupHeaderColumn]="hasGroupHeaderColumn"
29368
+ [groupHeaderColumns]="groupHeaderColumns"
29369
+ [rowIndex]="stickyItem.flatIndex + 1"
29370
+ [totalColumnsCount]="totalColumnsCount"
29371
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29372
+ [attr.data-group-index]="$any(stickyItem.item).index"
29373
+ [attr.data-group-level]="$any(stickyItem.item).level"
29374
+ tabindex="-1">
29375
+ </tr>
29376
+ }
29377
+ }
29378
+ @if (position === 'bottom') {
29379
+ @for (stickyItem of items; track stickyItem.flatIndex; let rowIdx = $index) {
29380
+ <tr role="row"
29381
+ class="k-group-footer"
29382
+ [attr.aria-rowindex]="stickyItem.flatIndex + 1"
29383
+ [attr.data-sticky-row]="rowIdx">
29384
+ @if (!isLocked) {
29385
+ @for (g of groups; track $index) {
29386
+ <td class="k-group-cell k-table-td k-table-group-td" role="presentation"></td>
29387
+ }
29388
+ @if (detailTemplate?.templateRef && !isStacked) {
29389
+ <td class="k-hierarchy-cell k-table-td" role="gridcell"
29390
+ aria-selected="false">
29391
+ </td>
29392
+ }
29393
+ }
29394
+ @if (!isStacked) {
29395
+ @for (column of footerColumns; track $index; let columnIndex = $index) {
29396
+ <td role="gridcell"
29397
+ class="k-table-td"
29398
+ tabindex="-1"
29399
+ [attr.aria-colindex]="columnIndex + 1"
29400
+ [attr.data-col-index]="$any(column).leafIndex">
29401
+ <ng-template
29402
+ [templateContext]="{
29403
+ templateRef: $any(column).groupFooterTemplateRef,
29404
+ group: $any(stickyItem.item).data,
29405
+ field: $any(column).field,
29406
+ column: column,
29407
+ aggregates: $any(stickyItem.item).data?.aggregates,
29408
+ $implicit: $any(stickyItem.item).data?.aggregates
29409
+ }">
29410
+ </ng-template>
29411
+ </td>
29412
+ }
29413
+ } @else {
29414
+ <td role="gridcell" class="k-table-td">
29415
+ <div class="k-grid-column-template">
29416
+ @for (col of footerColumns; track $index) {
29417
+ @if ($any(col).groupFooterTemplateRef) {
29418
+ <div class="k-column-template-item">
29419
+ <ng-template
29420
+ [templateContext]="{
29421
+ templateRef: $any(col).groupFooterTemplateRef,
29422
+ group: $any(stickyItem.item).data,
29423
+ field: $any(col).field,
29424
+ column: col,
29425
+ aggregates: $any(stickyItem.item).data?.aggregates,
29426
+ $implicit: $any(stickyItem.item).data?.aggregates
29427
+ }">
29428
+ </ng-template>
29429
+ </div>
29430
+ }
29431
+ }
29432
+ </div>
29433
+ </td>
29434
+ }
29435
+ </tr>
29436
+ }
29437
+ }
29438
+ </tbody>
29439
+ </table>
29440
+ </div>
29441
+ `,
29442
+ standalone: true,
29443
+ imports: [
29444
+ ColGroupComponent,
29445
+ GroupHeaderComponent,
29446
+ TemplateContextDirective
29447
+ ],
29448
+ providers: [
29449
+ { provide: SKIP_CELL_NAVIGATION, useValue: true }
29450
+ ]
29451
+ }]
29452
+ }], ctorParameters: () => [{ type: StickyGroupsService }, { type: i0.ChangeDetectorRef }, { type: ContextService }, { type: i0.ElementRef }, { type: NavigationService }, { type: GroupsService }, { type: i0.NgZone }], propDecorators: { position: [{
29453
+ type: Input
29454
+ }], columns: [{
29455
+ type: Input
29456
+ }], lockedColumns: [{
29457
+ type: Input
29458
+ }], groups: [{
29459
+ type: Input
29460
+ }], detailTemplate: [{
29461
+ type: Input
29462
+ }], totalColumnsCount: [{
29463
+ type: Input
29464
+ }], hasGroupHeaderColumn: [{
29465
+ type: Input
29466
+ }], groupHeaderColumns: [{
29467
+ type: Input
29468
+ }], tableWidth: [{
29469
+ type: Input
29470
+ }], lockedWidth: [{
29471
+ type: Input
29472
+ }], isLocked: [{
29473
+ type: Input
29474
+ }], hostClass: [{
29475
+ type: HostBinding,
29476
+ args: ['class.k-grid-sticky-container']
29477
+ }], isBottom: [{
29478
+ type: HostBinding,
29479
+ args: ['class.k-pos-bottom']
29480
+ }], hostRole: [{
29481
+ type: HostBinding,
29482
+ args: ['attr.role']
29483
+ }], onContainerClick: [{
29484
+ type: HostListener,
29485
+ args: ['click', ['$event']]
29486
+ }], onContainerFocusIn: [{
29487
+ type: HostListener,
29488
+ args: ['focusin', ['$event']]
29489
+ }], onContainerFocusOut: [{
29490
+ type: HostListener,
29491
+ args: ['focusout', ['$event']]
29492
+ }], onContainerKeydown: [{
29493
+ type: HostListener,
29494
+ args: ['keydown', ['$event']]
29495
+ }] } });
29496
+
27604
29497
  const elementAt = (index, elements, elementOffset) => {
27605
29498
  for (let idx = 0, elementIdx = 0; idx < elements.length; idx++) {
27606
29499
  const offset = elementOffset(elements[idx]);
@@ -27666,6 +29559,7 @@ class ListComponent {
27666
29559
  pdfService;
27667
29560
  columnInfo;
27668
29561
  dataMappingService;
29562
+ stickyGroupsService;
27669
29563
  hostClass = true;
27670
29564
  hostRole = 'presentation';
27671
29565
  data;
@@ -27697,6 +29591,19 @@ class ListComponent {
27697
29591
  columnsStartIdx = 0;
27698
29592
  allItems = [];
27699
29593
  itemsToRender = [];
29594
+ stickyGroupHeaderColumns = [];
29595
+ stickyNeedsUpdate = false;
29596
+ stickyWasEnabled = false;
29597
+ pendingToggleScroll = null;
29598
+ skipScrollerAdjust = false;
29599
+ suppressStickyUpdates = false;
29600
+ suppressFocusScrollAdjust = false;
29601
+ get hasStickyHeaders() {
29602
+ return this.groupable && this.groupable.stickyHeaders && this.groups?.length > 0;
29603
+ }
29604
+ get hasStickyFooters() {
29605
+ return this.groupable && this.groupable.stickyFooters && this.groups?.length > 0;
29606
+ }
27700
29607
  get showFooter() {
27701
29608
  return this.groupable && this.groupable.showFooter;
27702
29609
  }
@@ -27710,6 +29617,8 @@ class ListComponent {
27710
29617
  lockedTable;
27711
29618
  table;
27712
29619
  resizeSensors = new QueryList();
29620
+ stickyHeaderContainer;
29621
+ stickyFooterContainer;
27713
29622
  scroller;
27714
29623
  scrollerFactory;
27715
29624
  subscriptions;
@@ -27759,7 +29668,7 @@ class ListComponent {
27759
29668
  minRowHeight;
27760
29669
  handleSkipOnData = false;
27761
29670
  scrollToIndex = null;
27762
- constructor(scrollerFactory, detailsService, changeNotification, suspendService, groupsService, ngZone, renderer, scrollSyncService, resizeService, editService, supportService, navigationService, scrollRequestService, ctx, columnResizingService, changeDetector, pdfService, columnInfo, dataMappingService) {
29671
+ constructor(scrollerFactory, detailsService, changeNotification, suspendService, groupsService, ngZone, renderer, scrollSyncService, resizeService, editService, supportService, navigationService, scrollRequestService, ctx, columnResizingService, changeDetector, pdfService, columnInfo, dataMappingService, stickyGroupsService) {
27763
29672
  this.changeNotification = changeNotification;
27764
29673
  this.suspendService = suspendService;
27765
29674
  this.groupsService = groupsService;
@@ -27776,10 +29685,16 @@ class ListComponent {
27776
29685
  this.pdfService = pdfService;
27777
29686
  this.columnInfo = columnInfo;
27778
29687
  this.dataMappingService = dataMappingService;
29688
+ this.stickyGroupsService = stickyGroupsService;
27779
29689
  this.scrollerFactory = scrollerFactory;
27780
29690
  this.scroller = this.ctx.scroller = scrollerFactory(this.dispatcher, this.ctx);
27781
29691
  this.subscriptions = detailsService.changes.subscribe(() => this.detailExpand());
27782
- this.subscriptions.add(scrollRequestService.requests.subscribe(req => isPresent(req.adjustIndex) ? this.scrollTo(req.request, req.adjustIndex) : this.scrollToItem(req.request)));
29692
+ this.subscriptions.add(scrollRequestService.requests.subscribe(req => {
29693
+ if (this.suppressFocusScrollAdjust) {
29694
+ return;
29695
+ }
29696
+ isPresent(req.adjustIndex) ? this.scrollTo(req.request, req.adjustIndex) : this.scrollToItem(req.request);
29697
+ }));
27783
29698
  this.subscriptions.add(this.pdfService.restoreDOMVirtualization.subscribe(() => {
27784
29699
  this.ngZone.onStable.pipe(take(1)).subscribe(() => {
27785
29700
  this.init();
@@ -27789,7 +29704,8 @@ class ListComponent {
27789
29704
  }));
27790
29705
  }
27791
29706
  ngOnInit() {
27792
- this.minRowHeight = this.isVirtual ? this.rowHeight || calcRowHeight(this.table.nativeElement) : this.rowHeight;
29707
+ const needsMeasuredRowHeight = this.isVirtual || this.hasStickyHeaders || this.hasStickyFooters;
29708
+ this.minRowHeight = needsMeasuredRowHeight ? this.rowHeight || calcRowHeight(this.table.nativeElement) : this.rowHeight;
27793
29709
  this.init();
27794
29710
  this.subscriptions.add(this.ngZone.runOutsideAngular(this.handleRowSync.bind(this)));
27795
29711
  this.subscriptions.add(this.groupsService.changes.subscribe(() => {
@@ -27805,6 +29721,33 @@ class ListComponent {
27805
29721
  }
27806
29722
  }));
27807
29723
  this.subscriptions.add(this.ctx.localization.changes.subscribe(({ rtl }) => this.rtl = rtl));
29724
+ this.subscriptions.add(this.stickyGroupsService.scrollToFooter$.subscribe(() => {
29725
+ this.stickyNeedsUpdate = true;
29726
+ }));
29727
+ this.subscriptions.add(this.stickyGroupsService.scrollToRow$.subscribe(() => {
29728
+ this.stickyNeedsUpdate = true;
29729
+ }));
29730
+ this.subscriptions.add(this.stickyGroupsService.scrollAndToggle$.subscribe((groupIndex) => {
29731
+ this.handleStickyScrollAndToggle(groupIndex);
29732
+ }));
29733
+ this.subscriptions.add(this.ngZone.runOutsideAngular(() => {
29734
+ const navSub = this.navigationService.changes.subscribe(() => {
29735
+ if (!this.hasStickyHeaders && !this.hasStickyFooters) {
29736
+ return;
29737
+ }
29738
+ requestAnimationFrame(() => {
29739
+ if (this.suppressFocusScrollAdjust) {
29740
+ return;
29741
+ }
29742
+ this.stickyGroupsService.adjustScrollForFocusedCell(this.container?.nativeElement, this.stickyHeaderContainer?.nativeElement, this.stickyFooterContainer?.nativeElement);
29743
+ });
29744
+ });
29745
+ return {
29746
+ unsubscribe: () => {
29747
+ navSub.unsubscribe();
29748
+ }
29749
+ };
29750
+ }));
27808
29751
  }
27809
29752
  ngOnChanges(changes) {
27810
29753
  if (!isDocumentAvailable()) {
@@ -27869,6 +29812,11 @@ class ListComponent {
27869
29812
  else if (this.totalIsAllItems && totalChanged) {
27870
29813
  this.scroller.reset(this.skipScroll);
27871
29814
  this.scroller.total = this.allItems.length;
29815
+ // When a sticky toggle is pending, use the pre-computed virtualSkip
29816
+ // (calculated from the OLD measured RHS before toggle).
29817
+ if (this.pendingToggleScroll && this.virtualPageSize) {
29818
+ this.scroller.virtualSkip = this.pendingToggleScroll.virtualSkip;
29819
+ }
27872
29820
  this.itemsToRender = this.allItems.slice(this.scroller.virtualSkip, this.scroller.virtualSkip + this.virtualPageSize);
27873
29821
  }
27874
29822
  else if (totalChanged && !this.ctx.grid?.group?.length) {
@@ -27931,19 +29879,21 @@ class ListComponent {
27931
29879
  this.scroller.total = this.allItems.length;
27932
29880
  this.rowHeightService = this.scroller.rowHeightService = new RowHeightService(this.scroller.total, this.rowHeight || this.minRowHeight);
27933
29881
  }
27934
- this.isVirtual && this.scroller.update();
29882
+ this.isVirtual && this.scroller.update(this.skipScrollerAdjust);
29883
+ this.skipScrollerAdjust = false;
27935
29884
  // Item outside of the viewport is scrolled to programmatically.
27936
29885
  // RowHeightService offset for given index still may not match the current scrollTop
27937
29886
  // depending on the varying row heights, so we need to adjust the scroll position.
27938
29887
  if (isPresent(this.scrollToIndex)) {
27939
29888
  const offset = this.scroller.rowHeightService.offset(this.scrollToIndex);
29889
+ const targetScrollTop = this.getStickyAdjustedScrollTop(offset);
27940
29890
  const el = this.container.nativeElement;
27941
29891
  const maxScrollTop = el.scrollHeight - el.clientHeight;
27942
29892
  // Only adjust if scrollTop can actually increase; otherwise adjustScroll
27943
29893
  // sets scrollSyncing=true but the scrollTop stays clamped, which blocks
27944
29894
  // the pending scroll event from being processed by the scroller.
27945
- if (offset > el.scrollTop && el.scrollTop < maxScrollTop) {
27946
- this.scroller.adjustScroll(offset - el.scrollTop);
29895
+ if (targetScrollTop > el.scrollTop && el.scrollTop < maxScrollTop) {
29896
+ this.scroller.adjustScroll(targetScrollTop - el.scrollTop);
27947
29897
  }
27948
29898
  this.scrollToIndex = null;
27949
29899
  }
@@ -27961,6 +29911,27 @@ class ListComponent {
27961
29911
  this.zoneSub = null;
27962
29912
  });
27963
29913
  }
29914
+ const stickyEnabled = this.hasStickyHeaders || this.hasStickyFooters;
29915
+ if (stickyEnabled) {
29916
+ this.updateStickyGroupHeaderColumns();
29917
+ if (totalChanged || dataContentChanged) {
29918
+ this.stickyGroupsService.invalidateRangeMap();
29919
+ this.stickyNeedsUpdate = true;
29920
+ // Rebuild RowHeightService with the new allItems length so
29921
+ // offsets stay in sync after group expand/collapse.
29922
+ this.rowHeightService = this.scroller.rowHeightService =
29923
+ new RowHeightService(this.allItems.length, this.rowHeight || this.minRowHeight);
29924
+ }
29925
+ }
29926
+ else if (this.stickyWasEnabled) {
29927
+ this.stickyGroupsService.clear();
29928
+ const containerEl = this.container?.nativeElement;
29929
+ if (containerEl) {
29930
+ containerEl.style.scrollPaddingTop = '';
29931
+ containerEl.style.scrollPaddingBottom = '';
29932
+ }
29933
+ }
29934
+ this.stickyWasEnabled = stickyEnabled;
27964
29935
  }
27965
29936
  ngAfterViewInit() {
27966
29937
  if (!isDocumentAvailable()) {
@@ -27976,6 +29947,28 @@ class ListComponent {
27976
29947
  this.syncRowsHeight();
27977
29948
  }
27978
29949
  this.hasLockedContainer = isLocked;
29950
+ if (this.pendingToggleScroll) {
29951
+ this.applyPendingToggleScroll();
29952
+ }
29953
+ if (this.stickyNeedsUpdate && isDocumentAvailable()) {
29954
+ this.stickyNeedsUpdate = false;
29955
+ this.updateStickyRowHeights();
29956
+ this.scrollToStickyFooterTarget();
29957
+ if (this.suppressFocusScrollAdjust) {
29958
+ // After a toggle, the row is already positioned correctly —
29959
+ // just focus it without scrolling.
29960
+ const target = this.stickyGroupsService.consumeScrollToRow();
29961
+ if (target) {
29962
+ this.focusRenderedStickyRow(target.flatIndex, target.colIndex);
29963
+ }
29964
+ }
29965
+ else {
29966
+ this.scrollToStickyRowTarget();
29967
+ }
29968
+ if (!this.suppressStickyUpdates) {
29969
+ this.updateStickyGroups();
29970
+ }
29971
+ }
27979
29972
  }
27980
29973
  syncRowsHeight() {
27981
29974
  if (this.lockedContainer) {
@@ -28000,7 +29993,16 @@ class ListComponent {
28000
29993
  if (this.suspendService.scroll) {
28001
29994
  return;
28002
29995
  }
28003
- const total = this.isVirtual && this.ctx.grid?.pageable ? this.ctx.grid.pageSize : this.total;
29996
+ let total;
29997
+ if (this.isVirtual && this.ctx.grid?.pageable) {
29998
+ total = this.ctx.grid.pageSize;
29999
+ }
30000
+ else if (this.hasStickyHeaders || this.hasStickyFooters) {
30001
+ total = this.allItems.length || this.total;
30002
+ }
30003
+ else {
30004
+ total = this.total;
30005
+ }
28004
30006
  this.rowHeightService = this.scroller.rowHeightService = new RowHeightService(total, this.rowHeight || this.minRowHeight);
28005
30007
  if (!isUniversal()) {
28006
30008
  if (this.skipScroll) {
@@ -28079,6 +30081,41 @@ class ListComponent {
28079
30081
  this.itemsToRender = this.allItems.slice(this.scroller.virtualSkip, newEnd);
28080
30082
  }
28081
30083
  }
30084
+ /**
30085
+ * Recalculates the virtual page size after the scrollable container height changes
30086
+ * due to pinned row containers appearing or disappearing. Expands itemsToRender
30087
+ * if the new viewport can display more rows than currently rendered.
30088
+ * @hidden
30089
+ */
30090
+ onPinnedContainerHeightChange() {
30091
+ if (!this.isVirtual || !this.container || !this.scroller?.rowHeightService) {
30092
+ return;
30093
+ }
30094
+ // For non-pageable non-grouped virtual, itemsToRender always equals allItems
30095
+ if (!this.ctx.grid?.pageable && !this.ctx.grid?.group?.length) {
30096
+ return;
30097
+ }
30098
+ const newPageSize = this.calcVirtualPageSize();
30099
+ if (newPageSize <= 0 || newPageSize <= this.virtualPageSize) {
30100
+ return;
30101
+ }
30102
+ this.virtualPageSize = this.scroller.virtualPageSize = newPageSize;
30103
+ // Defer the itemsToRender update to a new change detection cycle
30104
+ // to avoid NG0100 (expression changed after check) when this
30105
+ // method is called from onStable after the initial CD pass.
30106
+ this.ngZone.runOutsideAngular(() => {
30107
+ setTimeout(() => {
30108
+ this.ngZone.run(() => {
30109
+ const newEnd = Math.min(this.scroller.virtualSkip + newPageSize, this.allItems.length);
30110
+ const currentEnd = this.scroller.virtualSkip + this.itemsToRender.length;
30111
+ if (newEnd > currentEnd) {
30112
+ this.itemsToRender = this.allItems.slice(this.scroller.virtualSkip, newEnd);
30113
+ }
30114
+ this.scroller.update();
30115
+ });
30116
+ });
30117
+ });
30118
+ }
28082
30119
  /**
28083
30120
  * Checks if the virtual scroll state is out of sync with the expected position.
28084
30121
  * Used to determine if resetVirtualScroll should be called.
@@ -28150,6 +30187,392 @@ class ListComponent {
28150
30187
  get isStacked() {
28151
30188
  return this.ctx.grid?.isStacked;
28152
30189
  }
30190
+ /**
30191
+ * @hidden
30192
+ * Updates sticky group header columns for the overlay. Called in ngDoCheck or on column changes.
30193
+ */
30194
+ updateStickyGroupHeaderColumns() {
30195
+ if (this.columns.hasGroupHeaderColumn) {
30196
+ this.stickyGroupHeaderColumns = columnsToRender(this.nonLockedLeafColumns.toArray().slice(1));
30197
+ }
30198
+ else {
30199
+ this.stickyGroupHeaderColumns = [];
30200
+ }
30201
+ }
30202
+ /**
30203
+ * Measures actual rendered row heights from the DOM and feeds them into
30204
+ * RowHeightService so its offsets reflect reality. Called in ngAfterViewChecked
30205
+ * when stickyNeedsUpdate is true (data changed or scroll-to-group requested).
30206
+ */
30207
+ updateStickyRowHeights() {
30208
+ if (!this.rowHeightService) {
30209
+ return;
30210
+ }
30211
+ const tableEl = this.table?.nativeElement;
30212
+ const tbody = tableEl?.querySelector('tbody');
30213
+ if (!tbody?.children.length) {
30214
+ return;
30215
+ }
30216
+ const heights = [];
30217
+ for (let i = 0; i < tbody.children.length; i++) {
30218
+ heights.push(tbody.children[i].getBoundingClientRect().height);
30219
+ }
30220
+ this.rowHeightService.update(this.scroller?.virtualSkip ?? 0, heights);
30221
+ }
30222
+ /**
30223
+ * Handles sticky overlay header click: scrolls to the topmost sticky
30224
+ * group header's real position so all overlay headers become visible
30225
+ * real rows, then toggles the clicked group.
30226
+ */
30227
+ handleStickyScrollAndToggle(groupIndex) {
30228
+ const containerEl = this.container?.nativeElement;
30229
+ if (!containerEl) {
30230
+ return;
30231
+ }
30232
+ const flatIndex = this.allItems.findIndex(item => item.type === 'group' && item.index === groupIndex);
30233
+ if (flatIndex < 0) {
30234
+ return;
30235
+ }
30236
+ const groupItem = this.allItems[flatIndex];
30237
+ // Find the topmost (root) sticky header — scroll to its real position
30238
+ // so all overlay headers become visible real rows at the top.
30239
+ const stickyItems = this.stickyGroupsService.stickyHeaderItems;
30240
+ const rootStickyItem = stickyItems.length > 0 ? stickyItems[0] : null;
30241
+ const isFirstChild = groupIndex.endsWith('_0');
30242
+ let scrollToIndex;
30243
+ let targetScrollTop;
30244
+ if (isFirstChild) {
30245
+ scrollToIndex = rootStickyItem ? rootStickyItem.flatIndex : flatIndex;
30246
+ targetScrollTop = this.rowHeightService
30247
+ ? Math.max(0, this.rowHeightService.offset(scrollToIndex))
30248
+ : containerEl.scrollTop;
30249
+ }
30250
+ else {
30251
+ // Non-first child: scroll so the clicked group will sit just below ancestor headers
30252
+ const parts = groupIndex.split('_');
30253
+ let ancestorHeight = 0;
30254
+ for (let level = 0; level < parts.length - 1; level++) {
30255
+ const ancestorIndex = parts.slice(0, level + 1).join('_');
30256
+ const ancestorFlatIdx = this.allItems.findIndex(item => item.type === 'group' && item.index === ancestorIndex);
30257
+ if (ancestorFlatIdx >= 0 && this.rowHeightService) {
30258
+ ancestorHeight += this.rowHeightService.height(ancestorFlatIdx);
30259
+ }
30260
+ }
30261
+ const clickedOffset = this.rowHeightService
30262
+ ? this.rowHeightService.offset(flatIndex)
30263
+ : containerEl.scrollTop;
30264
+ targetScrollTop = Math.max(0, clickedOffset - ancestorHeight);
30265
+ }
30266
+ // Compute virtual page info using the OLD measured RHS (before toggle destroys it)
30267
+ let virtualSkip = this.scroller.virtualSkip;
30268
+ let translateOffset = this.scroller.tableTransformOffset;
30269
+ if (this.isVirtual && this.virtualPageSize && this.rowHeightService) {
30270
+ const firstVisibleIdx = this.rowHeightService.index(targetScrollTop);
30271
+ const buffer = Math.max(Math.floor(this.virtualPageSize * 0.3), 0);
30272
+ virtualSkip = Math.max(firstVisibleIdx - buffer, 0);
30273
+ translateOffset = this.rowHeightService.offset(virtualSkip);
30274
+ }
30275
+ // 1. Suppress scroller from reacting to the scroll position change
30276
+ this.scroller.scrollSyncing = true;
30277
+ // Suppress scroll-triggered sticky updates until RAF completes
30278
+ this.suppressStickyUpdates = true;
30279
+ // 2. Scroll BEFORE toggle — pre-toggle DOM/data are still intact
30280
+ containerEl.scrollTop = targetScrollTop;
30281
+ const lockedEl = this.lockedContainer?.nativeElement;
30282
+ if (lockedEl) {
30283
+ lockedEl.scrollTop = targetScrollTop;
30284
+ }
30285
+ // 3. Store pending state for virtual page fix-up in ngAfterViewChecked
30286
+ this.pendingToggleScroll = { groupIndex, targetScrollTop, virtualSkip, translateOffset };
30287
+ // 4. Toggle — triggers groupsService.changes → skipScroll=true → ngDoCheck rebuilds allItems
30288
+ this.groupsService.toggleRow(groupItem);
30289
+ }
30290
+ /**
30291
+ * After toggle, fixes up the virtual page so the correct rows are rendered
30292
+ * around the target scroll position that was set pre-toggle.
30293
+ */
30294
+ applyPendingToggleScroll() {
30295
+ const pending = this.pendingToggleScroll;
30296
+ this.pendingToggleScroll = null;
30297
+ if (!pending) {
30298
+ return;
30299
+ }
30300
+ const containerEl = this.container?.nativeElement;
30301
+ if (!containerEl) {
30302
+ return;
30303
+ }
30304
+ // Clear skipScroll so scroller responds to future page changes.
30305
+ // Keep scrollSyncing = true so the async scroll event from the
30306
+ // pre-toggle scrollTop assignment is consumed harmlessly.
30307
+ this.skipScroll = false;
30308
+ this.scroller.scrollSyncing = true;
30309
+ const targetScrollTop = pending.targetScrollTop;
30310
+ // Ensure scrollTop is still at the target (scroller.reset may have nudged it)
30311
+ containerEl.scrollTop = targetScrollTop;
30312
+ // Virtual mode: apply pre-computed virtual page info (from OLD measured RHS)
30313
+ if (this.isVirtual && this.virtualPageSize) {
30314
+ const newVirtualSkip = pending.virtualSkip;
30315
+ const translateOffset = pending.translateOffset;
30316
+ this.scroller.virtualSkip = newVirtualSkip;
30317
+ this.scroller.firstToLoad = newVirtualSkip;
30318
+ this.scroller.lastLoaded = Math.min(newVirtualSkip + this.virtualPageSize, this.allItems.length) - 1;
30319
+ // Re-slice the visible window
30320
+ this.itemsToRender = this.allItems.slice(newVirtualSkip, newVirtualSkip + this.virtualPageSize);
30321
+ // Set table translateY using the pre-computed offset from the measured RHS
30322
+ this.scroller.tableTransformOffset = translateOffset;
30323
+ [
30324
+ this.table?.nativeElement,
30325
+ this.lockedTable?.nativeElement
30326
+ ].filter(isPresent).forEach(el => {
30327
+ this.renderer.setStyle(el, 'transform', `translateY(${translateOffset}px)`);
30328
+ });
30329
+ }
30330
+ // Sync locked container
30331
+ const lockedEl = this.lockedContainer?.nativeElement;
30332
+ if (lockedEl) {
30333
+ lockedEl.scrollTop = targetScrollTop;
30334
+ }
30335
+ // Suppress scroller.update() adjustScroll in the upcoming onStable callback
30336
+ this.skipScrollerAdjust = true;
30337
+ // Force sticky overlay update AFTER the DOM catches up.
30338
+ // At this point the DOM still has stale rows (tbodyRows != itemsToRender.length)
30339
+ // because Angular hasn't re-rendered the child table-body component yet.
30340
+ // Defer to requestAnimationFrame so the update sees correct DOM.
30341
+ this.stickyGroupsService.invalidateRangeMap();
30342
+ this.ngZone.runOutsideAngular(() => {
30343
+ requestAnimationFrame(() => {
30344
+ // After scroller.update() has measured actual row heights,
30345
+ // reconcile translateY with the updated RHS and recompute
30346
+ // scrollTop so the root group header sits at viewport top.
30347
+ if (this.isVirtual && this.rowHeightService) {
30348
+ const correctOffset = this.rowHeightService.offset(this.scroller.virtualSkip);
30349
+ const currentOffset = this.scroller.tableTransformOffset;
30350
+ if (Math.abs(currentOffset - correctOffset) > 1) {
30351
+ this.scroller.tableTransformOffset = correctOffset;
30352
+ [
30353
+ this.table?.nativeElement,
30354
+ this.lockedTable?.nativeElement
30355
+ ].filter(isPresent).forEach(el => {
30356
+ this.renderer.setStyle(el, 'transform', `translateY(${correctOffset}px)`);
30357
+ });
30358
+ }
30359
+ // Find the root sticky header and scroll to its updated offset
30360
+ const stickyItems = this.stickyGroupsService.stickyHeaderItems;
30361
+ const rootItem = stickyItems.length > 0 ? stickyItems[0] : null;
30362
+ const rootFlatIndex = rootItem ? rootItem.flatIndex : this.allItems.findIndex(item => item.type === 'group' && item.index === pending.groupIndex);
30363
+ const clickedFlatIndex = this.allItems.findIndex(item => item.type === 'group' && item.index === pending.groupIndex);
30364
+ const clickedOffset = clickedFlatIndex >= 0 ? this.rowHeightService.offset(clickedFlatIndex) : -1;
30365
+ const isFirstChild = pending.groupIndex.endsWith('_0');
30366
+ if (isFirstChild && rootFlatIndex >= 0) {
30367
+ // First child: scroll to root so all overlay items become real rows
30368
+ containerEl.scrollTop = this.rowHeightService.offset(rootFlatIndex);
30369
+ }
30370
+ else if (!isFirstChild && clickedFlatIndex >= 0) {
30371
+ // Non-first child: scroll so clicked group sits just below the overlay.
30372
+ // Overlay will contain all ancestor group headers (levels above the clicked group).
30373
+ const parts = pending.groupIndex.split('_');
30374
+ let ancestorHeight = 0;
30375
+ for (let level = 0; level < parts.length - 1; level++) {
30376
+ const ancestorIndex = parts.slice(0, level + 1).join('_');
30377
+ const ancestorFlatIdx = this.allItems.findIndex(item => item.type === 'group' && item.index === ancestorIndex);
30378
+ if (ancestorFlatIdx >= 0) {
30379
+ ancestorHeight += this.rowHeightService.height(ancestorFlatIdx);
30380
+ }
30381
+ }
30382
+ const targetScroll = Math.max(0, clickedOffset - ancestorHeight);
30383
+ containerEl.scrollTop = targetScroll;
30384
+ }
30385
+ this.scroller.scrollSyncing = true;
30386
+ const lockedEl = this.lockedContainer?.nativeElement;
30387
+ if (lockedEl) {
30388
+ lockedEl.scrollTop = containerEl.scrollTop;
30389
+ }
30390
+ }
30391
+ this.suppressStickyUpdates = false;
30392
+ this.updateStickyGroups();
30393
+ // Focus the toggled group header after DOM is settled
30394
+ const pendingFocus = this.stickyGroupsService.pendingFocusGroupIndex;
30395
+ if (pendingFocus) {
30396
+ this.stickyGroupsService.pendingFocusGroupIndex = null;
30397
+ const focusFlatIndex = this.allItems.findIndex(item => item.type === 'group' && item.index === pendingFocus);
30398
+ if (focusFlatIndex >= 0 && !this.stickyGroupsService.focusReady$.closed) {
30399
+ // Suppress the navigation-triggered adjustScrollForFocusedCell
30400
+ // since we already positioned the row correctly.
30401
+ this.suppressFocusScrollAdjust = true;
30402
+ this.ngZone.run(() => {
30403
+ this.stickyGroupsService.focusReady$.next({
30404
+ groupIndex: pendingFocus,
30405
+ flatIndex: focusFlatIndex
30406
+ });
30407
+ });
30408
+ // Clear after next frame so all queued nav-triggered RAFs see the flag
30409
+ requestAnimationFrame(() => {
30410
+ this.suppressFocusScrollAdjust = false;
30411
+ });
30412
+ }
30413
+ }
30414
+ });
30415
+ });
30416
+ }
30417
+ /**
30418
+ * Scrolls the container so the real footer row at the pending flat index
30419
+ * is visible at the bottom edge of the viewport.
30420
+ */
30421
+ scrollToStickyFooterTarget() {
30422
+ const flatIndex = this.stickyGroupsService.consumeScrollToFooter();
30423
+ if (flatIndex == null) {
30424
+ return;
30425
+ }
30426
+ const containerEl = this.container?.nativeElement;
30427
+ if (!containerEl || !this.rowHeightService) {
30428
+ return;
30429
+ }
30430
+ const targetOffset = this.rowHeightService.offset(flatIndex);
30431
+ const rowHeight = this.rowHeightService.height(flatIndex);
30432
+ const viewportHeight = containerEl.clientHeight;
30433
+ // Scroll so the footer row's bottom edge aligns with the viewport bottom
30434
+ containerEl.scrollTop = Math.max(0, targetOffset + rowHeight - viewportHeight);
30435
+ }
30436
+ /**
30437
+ * Scrolls the real grid body until the requested row is rendered in the real
30438
+ * tbody, then focuses the corresponding real grid cell.
30439
+ */
30440
+ scrollToStickyRowTarget() {
30441
+ const target = this.stickyGroupsService.consumeScrollToRow();
30442
+ if (!target) {
30443
+ return;
30444
+ }
30445
+ const tbody = this.getStickyTargetTbody(target.colIndex);
30446
+ if (!tbody || !this.rowHeightService) {
30447
+ return;
30448
+ }
30449
+ const logicalCell = this.stickyTargetToLogicalCell(target.flatIndex, target.colIndex);
30450
+ if (!logicalCell) {
30451
+ return;
30452
+ }
30453
+ const emitWhenRendered = () => {
30454
+ const start = this.scroller?.virtualSkip ?? 0;
30455
+ const end = start + this.itemsToRender.length - 1;
30456
+ const row = this.findStickyTargetRow(tbody, logicalCell.logicalRow);
30457
+ if (target.flatIndex < start || target.flatIndex > end || !row) {
30458
+ return false;
30459
+ }
30460
+ this.observer?.disconnect();
30461
+ this.focusRenderedStickyRow(target.flatIndex, target.colIndex);
30462
+ return true;
30463
+ };
30464
+ this.observer?.disconnect();
30465
+ this.scrollToVirtualRow(target.flatIndex, false);
30466
+ if (emitWhenRendered()) {
30467
+ return;
30468
+ }
30469
+ this.observer = new MutationObserver(() => {
30470
+ emitWhenRendered();
30471
+ });
30472
+ this.observer.observe(tbody, { childList: true, subtree: true });
30473
+ }
30474
+ /**
30475
+ * Focuses the rendered row requested from the sticky overlay.
30476
+ */
30477
+ focusRenderedStickyRow(flatIndex, colIndex) {
30478
+ const logicalCell = this.stickyTargetToLogicalCell(flatIndex, colIndex);
30479
+ if (!logicalCell) {
30480
+ return;
30481
+ }
30482
+ const tbody = this.getStickyTargetTbody(colIndex);
30483
+ const row = this.findStickyTargetRow(tbody, logicalCell.logicalRow);
30484
+ const targetCell = row?.querySelector(`[aria-colindex="${logicalCell.logicalCol + 1}"]`);
30485
+ const firstFocusableCell = row?.querySelector('[kendoGridLogicalCell], [kendogridlogicalcell], [aria-colindex]');
30486
+ let cell = targetCell
30487
+ ? this.navigationService.focusCellByElement(targetCell)
30488
+ : undefined;
30489
+ if (!cell && firstFocusableCell) {
30490
+ cell = this.navigationService.focusCellByElement(firstFocusableCell);
30491
+ }
30492
+ if (!cell) {
30493
+ cell = this.navigationService.focusCell(logicalCell.logicalRow, logicalCell.logicalCol);
30494
+ }
30495
+ if (cell && cell.colIndex !== logicalCell.logicalCol) {
30496
+ this.navigationService.focusCell(logicalCell.logicalRow, cell.colIndex);
30497
+ }
30498
+ }
30499
+ getStickyTargetTbody(colIndex) {
30500
+ const table = colIndex < this.lockedLeafColumns.length && this.lockedTable
30501
+ ? this.lockedTable.nativeElement
30502
+ : this.table?.nativeElement;
30503
+ return table?.querySelector('tbody');
30504
+ }
30505
+ findStickyTargetRow(tbody, logicalRow) {
30506
+ if (!tbody) {
30507
+ return null;
30508
+ }
30509
+ const rowIndex = String(logicalRow + 1);
30510
+ return tbody.querySelector(`tr[data-kendo-grid-row-index="${rowIndex}"], tr[aria-rowindex="${rowIndex}"]`);
30511
+ }
30512
+ /**
30513
+ * Converts a sticky overlay flat-index request into a logical grid cell
30514
+ * position that NavigationService can focus.
30515
+ */
30516
+ stickyTargetToLogicalCell(flatIndex, colIndex) {
30517
+ const meta = this.navigationService.metadata;
30518
+ if (!meta) {
30519
+ return null;
30520
+ }
30521
+ const pos = flatIndex + this.skip;
30522
+ const hasDetail = meta.hasDetailTemplate && !meta.isStacked;
30523
+ return {
30524
+ logicalRow: (hasDetail ? pos * 2 : pos) + meta.headerRows,
30525
+ logicalCol: colIndex + (hasDetail ? 1 : 0)
30526
+ };
30527
+ }
30528
+ updateStickyGroups(scrollTarget) {
30529
+ if (!this.hasStickyHeaders && !this.hasStickyFooters) {
30530
+ return;
30531
+ }
30532
+ if (this.suppressStickyUpdates) {
30533
+ return;
30534
+ }
30535
+ const containerEl = this.container?.nativeElement;
30536
+ if (!containerEl) {
30537
+ return;
30538
+ }
30539
+ // Get the tbody from the non-locked table
30540
+ const tableEl = this.table?.nativeElement;
30541
+ const tbody = tableEl?.querySelector('tbody');
30542
+ if (!tbody) {
30543
+ return;
30544
+ }
30545
+ const stickyHeaderEl = this.stickyHeaderContainer?.nativeElement || null;
30546
+ const stickyFooterEl = this.stickyFooterContainer?.nativeElement || null;
30547
+ this.stickyGroupsService.update({
30548
+ container: containerEl,
30549
+ tbody: tbody,
30550
+ flatData: this.allItems,
30551
+ stickyHeaderEl: stickyHeaderEl,
30552
+ stickyFooterEl: stickyFooterEl,
30553
+ enableHeaders: this.hasStickyHeaders,
30554
+ enableFooters: this.hasStickyFooters,
30555
+ rowHeight: this.rowHeight,
30556
+ skipOffset: this.isVirtual ? this.scroller?.virtualSkip : 0,
30557
+ rowHeightService: this.isVirtual ? this.rowHeightService : undefined
30558
+ });
30559
+ // Sync horizontal scroll for sticky containers
30560
+ if (scrollTarget) {
30561
+ const scrollLeft = scrollTarget.scrollLeft;
30562
+ if (stickyHeaderEl) {
30563
+ const scrollableWrapper = stickyHeaderEl.querySelector('.k-grid-header-wrap');
30564
+ if (scrollableWrapper) {
30565
+ scrollableWrapper.scrollLeft = scrollLeft;
30566
+ }
30567
+ }
30568
+ if (stickyFooterEl) {
30569
+ const scrollableWrapper = stickyFooterEl.querySelector('.k-grid-footer-wrap');
30570
+ if (scrollableWrapper) {
30571
+ scrollableWrapper.scrollLeft = scrollLeft;
30572
+ }
30573
+ }
30574
+ }
30575
+ }
28153
30576
  resetNavigationViewport() {
28154
30577
  if (!isDocumentAvailable()) {
28155
30578
  return;
@@ -28204,6 +30627,7 @@ class ListComponent {
28204
30627
  if (this.virtualColumns || this.isVirtual) {
28205
30628
  this.handleColumnScroll();
28206
30629
  }
30630
+ this.updateStickyGroups(target);
28207
30631
  const rowViewport = this.navigationService.viewport || EMPTY_OBJECT;
28208
30632
  const columnViewport = this.navigationService.columnViewport || EMPTY_OBJECT;
28209
30633
  this.contentScroll.emit({
@@ -28252,7 +30676,7 @@ class ListComponent {
28252
30676
  if (this.lockedContainer) {
28253
30677
  this.lockedContainer.nativeElement.scrollTop = scrollTop;
28254
30678
  }
28255
- // Sync translateY offset during rapid scrolling with virtual+paging to prevent blank grid
30679
+ // Sync translateY offset during rapid scrolling with virtual paging to prevent blank grid.
28256
30680
  if (this.isVirtual && this.ctx.grid?.pageable && this.scroller.rowHeightService) {
28257
30681
  const expectedFirstIndex = this.scroller.rowHeightService.index(scrollTop);
28258
30682
  const expectedOffset = this.scroller.rowHeightService.offset(expectedFirstIndex);
@@ -28308,9 +30732,22 @@ class ListComponent {
28308
30732
  itemIndex = Math.floor(itemIndex / 2);
28309
30733
  }
28310
30734
  const offset = this.rowHeightService.offset(itemIndex);
28311
- this.container.nativeElement.scrollTop = offset;
30735
+ const scrollTop = this.getStickyAdjustedScrollTop(offset);
30736
+ this.container.nativeElement.scrollTop = scrollTop;
28312
30737
  this.scrollToIndex = itemIndex;
28313
30738
  }
30739
+ getStickyAdjustedScrollTop(offset) {
30740
+ if (!this.hasStickyHeaders && !this.hasStickyFooters) {
30741
+ return offset;
30742
+ }
30743
+ const gridContainer = this.container?.nativeElement?.closest('.k-grid-container');
30744
+ const stickyHeaderEl = gridContainer
30745
+ ?.querySelector('.k-grid-sticky-container:not(.k-pos-bottom)');
30746
+ if (!stickyHeaderEl?.offsetHeight) {
30747
+ return offset;
30748
+ }
30749
+ return Math.max(0, offset - stickyHeaderEl.offsetHeight);
30750
+ }
28314
30751
  scrollTo({ row, column }, adjustIndex = false) {
28315
30752
  if (isNumber(row)) {
28316
30753
  if (this.isVirtual) {
@@ -28476,13 +30913,29 @@ class ListComponent {
28476
30913
  this.scroller.scrollHeightContainer = this.container.nativeElement.querySelector('.k-height-container');
28477
30914
  this.scroller.total = this.isVirtual && !this.ctx.grid?.pageable ? this.total : this.allItems.length;
28478
30915
  }
28479
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ListComponent, deps: [{ token: SCROLLER_FACTORY_TOKEN }, { token: DetailsService }, { token: ChangeNotificationService }, { token: SuspendService }, { token: GroupsService }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: ScrollSyncService }, { token: ResizeService }, { token: EditService }, { token: i1$3.ScrollbarService }, { token: NavigationService }, { token: ScrollRequestService }, { token: ContextService }, { token: ColumnResizingService }, { token: i0.ChangeDetectorRef }, { token: PDFService }, { token: ColumnInfoService }, { token: DataMappingService }], target: i0.ɵɵFactoryTarget.Component });
30916
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ListComponent, deps: [{ token: SCROLLER_FACTORY_TOKEN }, { token: DetailsService }, { token: ChangeNotificationService }, { token: SuspendService }, { token: GroupsService }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: ScrollSyncService }, { token: ResizeService }, { token: EditService }, { token: i1$3.ScrollbarService }, { token: NavigationService }, { token: ScrollRequestService }, { token: ContextService }, { token: ColumnResizingService }, { token: i0.ChangeDetectorRef }, { token: PDFService }, { token: ColumnInfoService }, { token: DataMappingService }, { token: StickyGroupsService }], target: i0.ɵɵFactoryTarget.Component });
28480
30917
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: ListComponent, isStandalone: true, selector: "kendo-grid-list", inputs: { data: "data", groups: "groups", total: "total", rowHeight: "rowHeight", detailRowHeight: "detailRowHeight", take: "take", skip: "skip", columns: "columns", detailTemplate: "detailTemplate", noRecordsTemplate: "noRecordsTemplate", selectable: "selectable", groupable: "groupable", filterable: "filterable", rowClass: "rowClass", rowSticky: "rowSticky", loading: "loading", trackBy: "trackBy", virtualColumns: "virtualColumns", isVirtual: "isVirtual", cellLoadingTemplate: "cellLoadingTemplate", loadingTemplate: "loadingTemplate", sort: "sort", size: "size" }, outputs: { contentScroll: "contentScroll", pageChange: "pageChange", scrollBottom: "scrollBottom" }, host: { properties: { "class.k-grid-container": "this.hostClass", "attr.role": "this.hostRole" } }, providers: [
28481
30918
  {
28482
30919
  provide: SCROLLER_FACTORY_TOKEN,
28483
30920
  useValue: DEFAULT_SCROLLER_FACTORY
28484
30921
  }
28485
- ], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }, { propertyName: "lockedContainer", first: true, predicate: ["lockedContainer"], descendants: true }, { propertyName: "lockedTable", first: true, predicate: ["lockedTable"], descendants: true }, { propertyName: "table", first: true, predicate: ["table"], descendants: true, static: true }, { propertyName: "resizeSensors", predicate: ResizeSensorComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
30922
+ ], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }, { propertyName: "lockedContainer", first: true, predicate: ["lockedContainer"], descendants: true }, { propertyName: "lockedTable", first: true, predicate: ["lockedTable"], descendants: true }, { propertyName: "table", first: true, predicate: ["table"], descendants: true, static: true }, { propertyName: "stickyHeaderContainer", first: true, predicate: ["stickyHeaderContainer"], descendants: true, read: StickyGroupContainerComponent }, { propertyName: "stickyFooterContainer", first: true, predicate: ["stickyFooterContainer"], descendants: true, read: StickyGroupContainerComponent }, { propertyName: "resizeSensors", predicate: ResizeSensorComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
30923
+ @if (hasStickyHeaders) {
30924
+ <div kendoGridStickyGroupContainer
30925
+ #stickyHeaderContainer
30926
+ position="top"
30927
+ [columns]="$any(nonLockedLeafColumns)"
30928
+ [lockedColumns]="$any(lockedLeafColumns)"
30929
+ [groups]="groups"
30930
+ [detailTemplate]="detailTemplate"
30931
+ [totalColumnsCount]="leafColumns.length"
30932
+ [hasGroupHeaderColumn]="columns.hasGroupHeaderColumn"
30933
+ [groupHeaderColumns]="stickyGroupHeaderColumns"
30934
+ [tableWidth]="nonLockedWidth"
30935
+ [lockedWidth]="lockedWidth"
30936
+ [isLocked]="isLocked">
30937
+ </div>
30938
+ }
28486
30939
  @if (isLocked && !isStacked) {
28487
30940
  <div #lockedContainer class="k-grid-content-locked" role="presentation"
28488
30941
  [style.width.px]="lockedWidth" tabindex="-1"
@@ -28608,7 +31061,23 @@ class ListComponent {
28608
31061
  </div>
28609
31062
  }
28610
31063
  </div>
28611
- `, isInline: true, dependencies: [{ kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "directive", type: TableDirective, selector: "[kendoGridResizableTable]", inputs: ["locked", "virtualColumns"] }, { kind: "directive", type: GridTableDirective, selector: "[kendoGridTable]", inputs: ["size"] }, { kind: "component", type: ColGroupComponent, selector: "[kendoGridColGroup]", inputs: ["columns", "groups", "detailTemplate", "sort"] }, { kind: "component", type: TableBodyComponent, selector: "[kendoGridTableBody]", inputs: ["columns", "allColumns", "groups", "detailTemplate", "noRecordsTemplate", "rowsToRender", "skip", "selectable", "filterable", "noRecordsText", "isLocked", "isLoading", "isVirtual", "cellLoadingTemplate", "skipGroupDecoration", "lockedColumnsCount", "totalColumnsCount", "virtualColumns", "trackBy", "rowSticky", "totalColumns", "rowClass", "rowHeight", "detailRowHeight", "isPinContainer", "pinPosition"] }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }, { kind: "directive", type: ResizableContainerDirective, selector: "[kendoGridResizableContainer]", inputs: ["lockedWidth", "kendoGridResizableContainer"] }] });
31064
+ @if (hasStickyFooters) {
31065
+ <div kendoGridStickyGroupContainer
31066
+ #stickyFooterContainer
31067
+ position="bottom"
31068
+ [columns]="$any(nonLockedLeafColumns)"
31069
+ [lockedColumns]="$any(lockedLeafColumns)"
31070
+ [groups]="groups"
31071
+ [detailTemplate]="detailTemplate"
31072
+ [totalColumnsCount]="leafColumns.length"
31073
+ [hasGroupHeaderColumn]="columns.hasGroupHeaderColumn"
31074
+ [groupHeaderColumns]="stickyGroupHeaderColumns"
31075
+ [tableWidth]="nonLockedWidth"
31076
+ [lockedWidth]="lockedWidth"
31077
+ [isLocked]="isLocked">
31078
+ </div>
31079
+ }
31080
+ `, isInline: true, dependencies: [{ kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "directive", type: TableDirective, selector: "[kendoGridResizableTable]", inputs: ["locked", "virtualColumns"] }, { kind: "directive", type: GridTableDirective, selector: "[kendoGridTable]", inputs: ["size"] }, { kind: "component", type: ColGroupComponent, selector: "[kendoGridColGroup]", inputs: ["columns", "groups", "detailTemplate", "sort"] }, { kind: "component", type: TableBodyComponent, selector: "[kendoGridTableBody]", inputs: ["columns", "allColumns", "groups", "detailTemplate", "noRecordsTemplate", "rowsToRender", "skip", "selectable", "filterable", "noRecordsText", "isLocked", "isLoading", "isVirtual", "cellLoadingTemplate", "skipGroupDecoration", "lockedColumnsCount", "totalColumnsCount", "virtualColumns", "trackBy", "rowSticky", "totalColumns", "rowClass", "rowHeight", "detailRowHeight", "isPinContainer", "pinPosition"] }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }, { kind: "directive", type: ResizableContainerDirective, selector: "[kendoGridResizableContainer]", inputs: ["lockedWidth", "kendoGridResizableContainer"] }, { kind: "component", type: StickyGroupContainerComponent, selector: "[kendoGridStickyGroupContainer]", inputs: ["position", "columns", "lockedColumns", "groups", "detailTemplate", "totalColumnsCount", "hasGroupHeaderColumn", "groupHeaderColumns", "tableWidth", "lockedWidth", "isLocked"] }] });
28612
31081
  }
28613
31082
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ListComponent, decorators: [{
28614
31083
  type: Component,
@@ -28621,6 +31090,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
28621
31090
  ],
28622
31091
  selector: 'kendo-grid-list',
28623
31092
  template: `
31093
+ @if (hasStickyHeaders) {
31094
+ <div kendoGridStickyGroupContainer
31095
+ #stickyHeaderContainer
31096
+ position="top"
31097
+ [columns]="$any(nonLockedLeafColumns)"
31098
+ [lockedColumns]="$any(lockedLeafColumns)"
31099
+ [groups]="groups"
31100
+ [detailTemplate]="detailTemplate"
31101
+ [totalColumnsCount]="leafColumns.length"
31102
+ [hasGroupHeaderColumn]="columns.hasGroupHeaderColumn"
31103
+ [groupHeaderColumns]="stickyGroupHeaderColumns"
31104
+ [tableWidth]="nonLockedWidth"
31105
+ [lockedWidth]="lockedWidth"
31106
+ [isLocked]="isLocked">
31107
+ </div>
31108
+ }
28624
31109
  @if (isLocked && !isStacked) {
28625
31110
  <div #lockedContainer class="k-grid-content-locked" role="presentation"
28626
31111
  [style.width.px]="lockedWidth" tabindex="-1"
@@ -28746,14 +31231,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
28746
31231
  </div>
28747
31232
  }
28748
31233
  </div>
31234
+ @if (hasStickyFooters) {
31235
+ <div kendoGridStickyGroupContainer
31236
+ #stickyFooterContainer
31237
+ position="bottom"
31238
+ [columns]="$any(nonLockedLeafColumns)"
31239
+ [lockedColumns]="$any(lockedLeafColumns)"
31240
+ [groups]="groups"
31241
+ [detailTemplate]="detailTemplate"
31242
+ [totalColumnsCount]="leafColumns.length"
31243
+ [hasGroupHeaderColumn]="columns.hasGroupHeaderColumn"
31244
+ [groupHeaderColumns]="stickyGroupHeaderColumns"
31245
+ [tableWidth]="nonLockedWidth"
31246
+ [lockedWidth]="lockedWidth"
31247
+ [isLocked]="isLocked">
31248
+ </div>
31249
+ }
28749
31250
  `,
28750
31251
  standalone: true,
28751
- imports: [EventsOutsideAngularDirective, TableDirective, GridTableDirective, ColGroupComponent, TableBodyComponent, ResizeSensorComponent, ResizableContainerDirective]
31252
+ imports: [EventsOutsideAngularDirective, TableDirective, GridTableDirective, ColGroupComponent, TableBodyComponent, ResizeSensorComponent, ResizableContainerDirective, StickyGroupContainerComponent]
28752
31253
  }]
28753
31254
  }], ctorParameters: () => [{ type: undefined, decorators: [{
28754
31255
  type: Inject,
28755
31256
  args: [SCROLLER_FACTORY_TOKEN]
28756
- }] }, { type: DetailsService }, { type: ChangeNotificationService }, { type: SuspendService }, { type: GroupsService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: ScrollSyncService }, { type: ResizeService }, { type: EditService }, { type: i1$3.ScrollbarService }, { type: NavigationService }, { type: ScrollRequestService }, { type: ContextService }, { type: ColumnResizingService }, { type: i0.ChangeDetectorRef }, { type: PDFService }, { type: ColumnInfoService }, { type: DataMappingService }], propDecorators: { hostClass: [{
31257
+ }] }, { type: DetailsService }, { type: ChangeNotificationService }, { type: SuspendService }, { type: GroupsService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: ScrollSyncService }, { type: ResizeService }, { type: EditService }, { type: i1$3.ScrollbarService }, { type: NavigationService }, { type: ScrollRequestService }, { type: ContextService }, { type: ColumnResizingService }, { type: i0.ChangeDetectorRef }, { type: PDFService }, { type: ColumnInfoService }, { type: DataMappingService }, { type: StickyGroupsService }], propDecorators: { hostClass: [{
28757
31258
  type: HostBinding,
28758
31259
  args: ['class.k-grid-container']
28759
31260
  }], hostRole: [{
@@ -28826,6 +31327,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
28826
31327
  }], resizeSensors: [{
28827
31328
  type: ViewChildren,
28828
31329
  args: [ResizeSensorComponent]
31330
+ }], stickyHeaderContainer: [{
31331
+ type: ViewChild,
31332
+ args: ['stickyHeaderContainer', { read: StickyGroupContainerComponent }]
31333
+ }], stickyFooterContainer: [{
31334
+ type: ViewChild,
31335
+ args: ['stickyFooterContainer', { read: StickyGroupContainerComponent }]
28829
31336
  }] } });
28830
31337
 
28831
31338
  /**
@@ -29699,8 +32206,16 @@ class GridMessages extends ComponentMessages {
29699
32206
  * Sets the screen-reader-only content for the pin column header.
29700
32207
  */
29701
32208
  pinColumnHeaderLabel;
32209
+ /**
32210
+ * Sets the label for the Grid row pin column icon.
32211
+ */
32212
+ rowPinLabel;
32213
+ /**
32214
+ * Sets the label for the Grid row unpin column icon.
32215
+ */
32216
+ rowUnpinLabel;
29702
32217
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GridMessages, deps: null, target: i0.ɵɵFactoryTarget.Directive });
29703
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: GridMessages, isStandalone: true, selector: "kendo-grid-messages-base", inputs: { groupPanelEmpty: "groupPanelEmpty", noRecords: "noRecords", pagerLabel: "pagerLabel", pagerFirstPage: "pagerFirstPage", pagerLastPage: "pagerLastPage", pagerPreviousPage: "pagerPreviousPage", pagerNextPage: "pagerNextPage", pagerPage: "pagerPage", pagerItemsPerPage: "pagerItemsPerPage", pagerOf: "pagerOf", pagerItems: "pagerItems", pagerPageNumberInputTitle: "pagerPageNumberInputTitle", pagerInputLabel: "pagerInputLabel", pagerSelectPage: "pagerSelectPage", filter: "filter", filterInputLabel: "filterInputLabel", filterMenuTitle: "filterMenuTitle", filterMenuOperatorsDropDownLabel: "filterMenuOperatorsDropDownLabel", filterMenuLogicDropDownLabel: "filterMenuLogicDropDownLabel", filterCellOperatorLabel: "filterCellOperatorLabel", booleanFilterCellLabel: "booleanFilterCellLabel", aiAssistantApplyButtonText: "aiAssistantApplyButtonText", aiAssistantToolbarToolText: "aiAssistantToolbarToolText", aiAssistantWindowTitle: "aiAssistantWindowTitle", aiAssistantWindowCloseTitle: "aiAssistantWindowCloseTitle", aiAssistantOutputCardTitle: "aiAssistantOutputCardTitle", aiAssistantOutputCardBodyContent: "aiAssistantOutputCardBodyContent", aiAssistantSelectionNotEnabled: "aiAssistantSelectionNotEnabled", aiAssistantSelectionRowModeRequired: "aiAssistantSelectionRowModeRequired", aiAssistantSelectionCellModeRequired: "aiAssistantSelectionCellModeRequired", aiAssistantWindowMaximizeTitle: "aiAssistantWindowMaximizeTitle", aiAssistantWindowMinimizeTitle: "aiAssistantWindowMinimizeTitle", aiAssistantWindowRestoreTitle: "aiAssistantWindowRestoreTitle", filterEqOperator: "filterEqOperator", filterNotEqOperator: "filterNotEqOperator", filterIsNullOperator: "filterIsNullOperator", filterIsNotNullOperator: "filterIsNotNullOperator", filterIsEmptyOperator: "filterIsEmptyOperator", filterIsNotEmptyOperator: "filterIsNotEmptyOperator", filterStartsWithOperator: "filterStartsWithOperator", filterContainsOperator: "filterContainsOperator", filterNotContainsOperator: "filterNotContainsOperator", filterEndsWithOperator: "filterEndsWithOperator", filterGteOperator: "filterGteOperator", filterGtOperator: "filterGtOperator", filterLteOperator: "filterLteOperator", filterLtOperator: "filterLtOperator", filterIsTrue: "filterIsTrue", filterIsFalse: "filterIsFalse", filterBooleanAll: "filterBooleanAll", adaptiveFilterOperatorsTitle: "adaptiveFilterOperatorsTitle", filterAfterOrEqualOperator: "filterAfterOrEqualOperator", filterAfterOperator: "filterAfterOperator", filterBeforeOperator: "filterBeforeOperator", filterBeforeOrEqualOperator: "filterBeforeOrEqualOperator", filterFilterButton: "filterFilterButton", filterClearButton: "filterClearButton", adaptiveCloseButtonTitle: "adaptiveCloseButtonTitle", adaptiveBackButtonTitle: "adaptiveBackButtonTitle", filterAndLogic: "filterAndLogic", filterOrLogic: "filterOrLogic", filterToolbarToolText: "filterToolbarToolText", loading: "loading", gridLabel: "gridLabel", columnMenu: "columnMenu", setColumnPosition: "setColumnPosition", columns: "columns", columnChooserSelectAll: "columnChooserSelectAll", columnChooserSearchLabel: "columnChooserSearchLabel", columnChooserSelectedColumnsCount: "columnChooserSelectedColumnsCount", columnsSubtitle: "columnsSubtitle", adaptiveFilterTitle: "adaptiveFilterTitle", adaptiveSortTitle: "adaptiveSortTitle", adaptiveGroupTitle: "adaptiveGroupTitle", filterClearAllButton: "filterClearAllButton", groupClearButton: "groupClearButton", sortClearButton: "sortClearButton", sortDoneButton: "sortDoneButton", groupDoneButton: "groupDoneButton", lock: "lock", unlock: "unlock", stick: "stick", unstick: "unstick", sortable: "sortable", sortAscending: "sortAscending", sortDescending: "sortDescending", autosizeThisColumn: "autosizeThisColumn", autosizeAllColumns: "autosizeAllColumns", sortedAscending: "sortedAscending", sortedDescending: "sortedDescending", sortedDefault: "sortedDefault", sortToolbarToolText: "sortToolbarToolText", columnsApply: "columnsApply", columnsReset: "columnsReset", detailExpand: "detailExpand", detailCollapse: "detailCollapse", filterDateToday: "filterDateToday", filterDateToggle: "filterDateToggle", filterNumericDecrement: "filterNumericDecrement", filterNumericIncrement: "filterNumericIncrement", selectionCheckboxLabel: "selectionCheckboxLabel", selectAllCheckboxLabel: "selectAllCheckboxLabel", checkboxColumnHeaderLabel: "checkboxColumnHeaderLabel", groupCollapse: "groupCollapse", groupExpand: "groupExpand", topToolbarLabel: "topToolbarLabel", bottomToolbarLabel: "bottomToolbarLabel", editToolbarToolText: "editToolbarToolText", saveToolbarToolText: "saveToolbarToolText", addToolbarToolText: "addToolbarToolText", cancelToolbarToolText: "cancelToolbarToolText", removeToolbarToolText: "removeToolbarToolText", excelExportToolbarToolText: "excelExportToolbarToolText", csvExportToolbarToolText: "csvExportToolbarToolText", pdfExportToolbarToolText: "pdfExportToolbarToolText", groupPanelLabel: "groupPanelLabel", dragRowHandleLabel: "dragRowHandleLabel", columnMenuFilterTabTitle: "columnMenuFilterTabTitle", columnMenuGeneralTabTitle: "columnMenuGeneralTabTitle", columnMenuColumnsTabTitle: "columnMenuColumnsTabTitle", groupChipMenuPrevious: "groupChipMenuPrevious", groupChipMenuNext: "groupChipMenuNext", groupToolbarToolText: "groupToolbarToolText", formValidationErrorText: "formValidationErrorText", removeConfirmationDialogTitle: "removeConfirmationDialogTitle", removeConfirmationDialogContent: "removeConfirmationDialogContent", removeConfirmationDialogConfirmText: "removeConfirmationDialogConfirmText", removeConfirmationDialogRejectText: "removeConfirmationDialogRejectText", externalEditingTitle: "externalEditingTitle", externalEditingAddTitle: "externalEditingAddTitle", externalEditingSaveText: "externalEditingSaveText", externalEditingCancelText: "externalEditingCancelText", multiCheckboxFilterSearchPlaceholder: "multiCheckboxFilterSearchPlaceholder", multiCheckboxFilterSelectAllLabel: "multiCheckboxFilterSelectAllLabel", multiCheckboxFilterSelectedItemsCount: "multiCheckboxFilterSelectedItemsCount", smartBoxSpeechToTextButton: "smartBoxSpeechToTextButton", smartBoxSubmitPromptButton: "smartBoxSubmitPromptButton", smartBoxSearchPlaceholder: "smartBoxSearchPlaceholder", smartBoxSemanticSearchPlaceholder: "smartBoxSemanticSearchPlaceholder", smartBoxAIAssistantPlaceholder: "smartBoxAIAssistantPlaceholder", smartBoxSuggestedPrompts: "smartBoxSuggestedPrompts", smartBoxNoPreviousSearches: "smartBoxNoPreviousSearches", smartBoxNoPreviousPrompts: "smartBoxNoPreviousPrompts", smartBoxPreviouslySearched: "smartBoxPreviouslySearched", smartBoxPreviouslyAsked: "smartBoxPreviouslyAsked", searchModeListItemText: "searchModeListItemText", searchModeListItemDescription: "searchModeListItemDescription", semanticSearchModeListItemText: "semanticSearchModeListItemText", semanticSearchModeListItemDescription: "semanticSearchModeListItemDescription", smartBoxSearchModePopupButton: "smartBoxSearchModePopupButton", smartBoxAIAssistantModePopupButton: "smartBoxAIAssistantModePopupButton", detailColumnHeaderLabel: "detailColumnHeaderLabel", dragColumnHeaderLabel: "dragColumnHeaderLabel", commandColumnHeaderLabel: "commandColumnHeaderLabel", pinnedTopRowsLabel: "pinnedTopRowsLabel", pinnedBottomRowsLabel: "pinnedBottomRowsLabel", pinMenuPinToTopText: "pinMenuPinToTopText", pinMenuPinToBottomText: "pinMenuPinToBottomText", pinMenuUnpinText: "pinMenuUnpinText", pinColumnHeaderLabel: "pinColumnHeaderLabel" }, usesInheritance: true, ngImport: i0 });
32218
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: GridMessages, isStandalone: true, selector: "kendo-grid-messages-base", inputs: { groupPanelEmpty: "groupPanelEmpty", noRecords: "noRecords", pagerLabel: "pagerLabel", pagerFirstPage: "pagerFirstPage", pagerLastPage: "pagerLastPage", pagerPreviousPage: "pagerPreviousPage", pagerNextPage: "pagerNextPage", pagerPage: "pagerPage", pagerItemsPerPage: "pagerItemsPerPage", pagerOf: "pagerOf", pagerItems: "pagerItems", pagerPageNumberInputTitle: "pagerPageNumberInputTitle", pagerInputLabel: "pagerInputLabel", pagerSelectPage: "pagerSelectPage", filter: "filter", filterInputLabel: "filterInputLabel", filterMenuTitle: "filterMenuTitle", filterMenuOperatorsDropDownLabel: "filterMenuOperatorsDropDownLabel", filterMenuLogicDropDownLabel: "filterMenuLogicDropDownLabel", filterCellOperatorLabel: "filterCellOperatorLabel", booleanFilterCellLabel: "booleanFilterCellLabel", aiAssistantApplyButtonText: "aiAssistantApplyButtonText", aiAssistantToolbarToolText: "aiAssistantToolbarToolText", aiAssistantWindowTitle: "aiAssistantWindowTitle", aiAssistantWindowCloseTitle: "aiAssistantWindowCloseTitle", aiAssistantOutputCardTitle: "aiAssistantOutputCardTitle", aiAssistantOutputCardBodyContent: "aiAssistantOutputCardBodyContent", aiAssistantSelectionNotEnabled: "aiAssistantSelectionNotEnabled", aiAssistantSelectionRowModeRequired: "aiAssistantSelectionRowModeRequired", aiAssistantSelectionCellModeRequired: "aiAssistantSelectionCellModeRequired", aiAssistantWindowMaximizeTitle: "aiAssistantWindowMaximizeTitle", aiAssistantWindowMinimizeTitle: "aiAssistantWindowMinimizeTitle", aiAssistantWindowRestoreTitle: "aiAssistantWindowRestoreTitle", filterEqOperator: "filterEqOperator", filterNotEqOperator: "filterNotEqOperator", filterIsNullOperator: "filterIsNullOperator", filterIsNotNullOperator: "filterIsNotNullOperator", filterIsEmptyOperator: "filterIsEmptyOperator", filterIsNotEmptyOperator: "filterIsNotEmptyOperator", filterStartsWithOperator: "filterStartsWithOperator", filterContainsOperator: "filterContainsOperator", filterNotContainsOperator: "filterNotContainsOperator", filterEndsWithOperator: "filterEndsWithOperator", filterGteOperator: "filterGteOperator", filterGtOperator: "filterGtOperator", filterLteOperator: "filterLteOperator", filterLtOperator: "filterLtOperator", filterIsTrue: "filterIsTrue", filterIsFalse: "filterIsFalse", filterBooleanAll: "filterBooleanAll", adaptiveFilterOperatorsTitle: "adaptiveFilterOperatorsTitle", filterAfterOrEqualOperator: "filterAfterOrEqualOperator", filterAfterOperator: "filterAfterOperator", filterBeforeOperator: "filterBeforeOperator", filterBeforeOrEqualOperator: "filterBeforeOrEqualOperator", filterFilterButton: "filterFilterButton", filterClearButton: "filterClearButton", adaptiveCloseButtonTitle: "adaptiveCloseButtonTitle", adaptiveBackButtonTitle: "adaptiveBackButtonTitle", filterAndLogic: "filterAndLogic", filterOrLogic: "filterOrLogic", filterToolbarToolText: "filterToolbarToolText", loading: "loading", gridLabel: "gridLabel", columnMenu: "columnMenu", setColumnPosition: "setColumnPosition", columns: "columns", columnChooserSelectAll: "columnChooserSelectAll", columnChooserSearchLabel: "columnChooserSearchLabel", columnChooserSelectedColumnsCount: "columnChooserSelectedColumnsCount", columnsSubtitle: "columnsSubtitle", adaptiveFilterTitle: "adaptiveFilterTitle", adaptiveSortTitle: "adaptiveSortTitle", adaptiveGroupTitle: "adaptiveGroupTitle", filterClearAllButton: "filterClearAllButton", groupClearButton: "groupClearButton", sortClearButton: "sortClearButton", sortDoneButton: "sortDoneButton", groupDoneButton: "groupDoneButton", lock: "lock", unlock: "unlock", stick: "stick", unstick: "unstick", sortable: "sortable", sortAscending: "sortAscending", sortDescending: "sortDescending", autosizeThisColumn: "autosizeThisColumn", autosizeAllColumns: "autosizeAllColumns", sortedAscending: "sortedAscending", sortedDescending: "sortedDescending", sortedDefault: "sortedDefault", sortToolbarToolText: "sortToolbarToolText", columnsApply: "columnsApply", columnsReset: "columnsReset", detailExpand: "detailExpand", detailCollapse: "detailCollapse", filterDateToday: "filterDateToday", filterDateToggle: "filterDateToggle", filterNumericDecrement: "filterNumericDecrement", filterNumericIncrement: "filterNumericIncrement", selectionCheckboxLabel: "selectionCheckboxLabel", selectAllCheckboxLabel: "selectAllCheckboxLabel", checkboxColumnHeaderLabel: "checkboxColumnHeaderLabel", groupCollapse: "groupCollapse", groupExpand: "groupExpand", topToolbarLabel: "topToolbarLabel", bottomToolbarLabel: "bottomToolbarLabel", editToolbarToolText: "editToolbarToolText", saveToolbarToolText: "saveToolbarToolText", addToolbarToolText: "addToolbarToolText", cancelToolbarToolText: "cancelToolbarToolText", removeToolbarToolText: "removeToolbarToolText", excelExportToolbarToolText: "excelExportToolbarToolText", csvExportToolbarToolText: "csvExportToolbarToolText", pdfExportToolbarToolText: "pdfExportToolbarToolText", groupPanelLabel: "groupPanelLabel", dragRowHandleLabel: "dragRowHandleLabel", columnMenuFilterTabTitle: "columnMenuFilterTabTitle", columnMenuGeneralTabTitle: "columnMenuGeneralTabTitle", columnMenuColumnsTabTitle: "columnMenuColumnsTabTitle", groupChipMenuPrevious: "groupChipMenuPrevious", groupChipMenuNext: "groupChipMenuNext", groupToolbarToolText: "groupToolbarToolText", formValidationErrorText: "formValidationErrorText", removeConfirmationDialogTitle: "removeConfirmationDialogTitle", removeConfirmationDialogContent: "removeConfirmationDialogContent", removeConfirmationDialogConfirmText: "removeConfirmationDialogConfirmText", removeConfirmationDialogRejectText: "removeConfirmationDialogRejectText", externalEditingTitle: "externalEditingTitle", externalEditingAddTitle: "externalEditingAddTitle", externalEditingSaveText: "externalEditingSaveText", externalEditingCancelText: "externalEditingCancelText", multiCheckboxFilterSearchPlaceholder: "multiCheckboxFilterSearchPlaceholder", multiCheckboxFilterSelectAllLabel: "multiCheckboxFilterSelectAllLabel", multiCheckboxFilterSelectedItemsCount: "multiCheckboxFilterSelectedItemsCount", smartBoxSpeechToTextButton: "smartBoxSpeechToTextButton", smartBoxSubmitPromptButton: "smartBoxSubmitPromptButton", smartBoxSearchPlaceholder: "smartBoxSearchPlaceholder", smartBoxSemanticSearchPlaceholder: "smartBoxSemanticSearchPlaceholder", smartBoxAIAssistantPlaceholder: "smartBoxAIAssistantPlaceholder", smartBoxSuggestedPrompts: "smartBoxSuggestedPrompts", smartBoxNoPreviousSearches: "smartBoxNoPreviousSearches", smartBoxNoPreviousPrompts: "smartBoxNoPreviousPrompts", smartBoxPreviouslySearched: "smartBoxPreviouslySearched", smartBoxPreviouslyAsked: "smartBoxPreviouslyAsked", searchModeListItemText: "searchModeListItemText", searchModeListItemDescription: "searchModeListItemDescription", semanticSearchModeListItemText: "semanticSearchModeListItemText", semanticSearchModeListItemDescription: "semanticSearchModeListItemDescription", smartBoxSearchModePopupButton: "smartBoxSearchModePopupButton", smartBoxAIAssistantModePopupButton: "smartBoxAIAssistantModePopupButton", detailColumnHeaderLabel: "detailColumnHeaderLabel", dragColumnHeaderLabel: "dragColumnHeaderLabel", commandColumnHeaderLabel: "commandColumnHeaderLabel", pinnedTopRowsLabel: "pinnedTopRowsLabel", pinnedBottomRowsLabel: "pinnedBottomRowsLabel", pinMenuPinToTopText: "pinMenuPinToTopText", pinMenuPinToBottomText: "pinMenuPinToBottomText", pinMenuUnpinText: "pinMenuUnpinText", pinColumnHeaderLabel: "pinColumnHeaderLabel", rowPinLabel: "rowPinLabel", rowUnpinLabel: "rowUnpinLabel" }, usesInheritance: true, ngImport: i0 });
29704
32219
  }
29705
32220
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GridMessages, decorators: [{
29706
32221
  type: Directive,
@@ -30027,6 +32542,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
30027
32542
  type: Input
30028
32543
  }], pinColumnHeaderLabel: [{
30029
32544
  type: Input
32545
+ }], rowPinLabel: [{
32546
+ type: Input
32547
+ }], rowUnpinLabel: [{
32548
+ type: Input
30030
32549
  }] } });
30031
32550
 
30032
32551
  /**
@@ -32642,6 +35161,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32642
35161
  class RowPinContainerComponent {
32643
35162
  localization;
32644
35163
  pinnedWrapper;
35164
+ lockedTable;
35165
+ pinnedTable;
32645
35166
  hostClass = true;
32646
35167
  get bottomClass() {
32647
35168
  return this.position === 'bottom';
@@ -32661,20 +35182,31 @@ class RowPinContainerComponent {
32661
35182
  groups;
32662
35183
  detailTemplate;
32663
35184
  filterable;
35185
+ rowHeight;
35186
+ detailRowHeight;
35187
+ loading;
35188
+ size;
32664
35189
  constructor(localization) {
32665
35190
  this.localization = localization;
32666
35191
  }
35192
+ ngAfterViewChecked() {
35193
+ if (this.isLocked && this.lockedTable && this.pinnedTable) {
35194
+ syncRowsHeight(this.lockedTable.nativeElement, this.pinnedTable.nativeElement);
35195
+ }
35196
+ }
32667
35197
  get ariaLabel() {
32668
35198
  const labelKey = this.position === 'top' ? 'pinnedTopRowsLabel' : 'pinnedBottomRowsLabel';
32669
35199
  return this.localization.get(labelKey);
32670
35200
  }
32671
35201
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: RowPinContainerComponent, deps: [{ token: i1$2.LocalizationService }], target: i0.ɵɵFactoryTarget.Component });
32672
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: RowPinContainerComponent, isStandalone: true, selector: "kendo-grid-rowpin-container", inputs: { leafColumns: "leafColumns", lockedLeafColumns: "lockedLeafColumns", nonLockedLeafColumns: "nonLockedLeafColumns", sort: "sort", position: "position", rowsToRender: "rowsToRender", totalColumns: "totalColumns", lockedWidth: "lockedWidth", nonLockedWidth: "nonLockedWidth", isLocked: "isLocked", selectable: "selectable", trackBy: "trackBy", groups: "groups", detailTemplate: "detailTemplate", filterable: "filterable" }, host: { properties: { "class.k-grid-pinned-container": "this.hostClass", "class.k-pos-bottom": "this.bottomClass" } }, providers: [{ provide: IS_PIN_CONTAINER, useValue: true }], viewQueries: [{ propertyName: "pinnedWrapper", first: true, predicate: ["pinnedWrapper"], descendants: true, static: true }], ngImport: i0, template: `
35202
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: RowPinContainerComponent, isStandalone: true, selector: "kendo-grid-rowpin-container", inputs: { leafColumns: "leafColumns", lockedLeafColumns: "lockedLeafColumns", nonLockedLeafColumns: "nonLockedLeafColumns", sort: "sort", position: "position", rowsToRender: "rowsToRender", totalColumns: "totalColumns", lockedWidth: "lockedWidth", nonLockedWidth: "nonLockedWidth", isLocked: "isLocked", selectable: "selectable", trackBy: "trackBy", groups: "groups", detailTemplate: "detailTemplate", filterable: "filterable", rowHeight: "rowHeight", detailRowHeight: "detailRowHeight", loading: "loading", size: "size" }, host: { properties: { "class.k-grid-pinned-container": "this.hostClass", "class.k-pos-bottom": "this.bottomClass" } }, providers: [{ provide: IS_PIN_CONTAINER, useValue: true }], viewQueries: [{ propertyName: "pinnedWrapper", first: true, predicate: ["pinnedWrapper"], descendants: true, static: true }, { propertyName: "lockedTable", first: true, predicate: ["lockedTable"], descendants: true }, { propertyName: "pinnedTable", first: true, predicate: ["pinnedTable"], descendants: true, static: true }], ngImport: i0, template: `
32673
35203
  @if (isLocked) {
32674
35204
  <div class="k-grid-content-locked" role="presentation" [style.width.px]="lockedWidth">
32675
- <table class="k-grid-table"
35205
+ <table #lockedTable class="k-grid-table"
32676
35206
  kendoGridTable
32677
35207
  kendoGridResizableTable
35208
+ [locked]="true"
35209
+ [size]="size"
32678
35210
  [style.width.px]="lockedWidth">
32679
35211
  <colgroup kendoGridColGroup
32680
35212
  [groups]="groups"
@@ -32687,7 +35219,7 @@ class RowPinContainerComponent {
32687
35219
  [isLocked]="true"
32688
35220
  [rowsToRender]="rowsToRender"
32689
35221
  [columns]="$any(lockedLeafColumns)"
32690
- [totalColumnsCount]="totalColumns.leafColumns.length"
35222
+ [totalColumnsCount]="leafColumns.length"
32691
35223
  [totalColumns]="totalColumns"
32692
35224
  [selectable]="selectable"
32693
35225
  [trackBy]="trackBy"
@@ -32695,13 +35227,16 @@ class RowPinContainerComponent {
32695
35227
  [pinPosition]="position"
32696
35228
  [groups]="groups"
32697
35229
  [detailTemplate]="detailTemplate"
32698
- [filterable]="filterable">
35230
+ [filterable]="filterable"
35231
+ [rowHeight]="rowHeight"
35232
+ [detailRowHeight]="detailRowHeight"
35233
+ [isLoading]="loading">
32699
35234
  </tbody>
32700
35235
  </table>
32701
35236
  </div>
32702
35237
  }
32703
35238
  <div #pinnedWrapper class="k-grid-pinned-wrap">
32704
- <table class="k-grid-table"
35239
+ <table #pinnedTable class="k-grid-table"
32705
35240
  kendoGridTable
32706
35241
  kendoGridResizableTable
32707
35242
  [style.width.px]="nonLockedWidth"
@@ -32709,11 +35244,12 @@ class RowPinContainerComponent {
32709
35244
  <colgroup kendoGridColGroup
32710
35245
  [columns]="$any(nonLockedLeafColumns)"
32711
35246
  [sort]="sort"
32712
- [groups]="groups"
35247
+ [groups]="isLocked ? [] : groups"
32713
35248
  [detailTemplate]="detailTemplate">
32714
35249
  </colgroup>
32715
35250
  <tbody kendoGridTableBody
32716
35251
  role="presentation"
35252
+ [skipGroupDecoration]="isLocked"
32717
35253
  [rowsToRender]="rowsToRender"
32718
35254
  [columns]="$any(nonLockedLeafColumns)"
32719
35255
  [lockedColumnsCount]="isLocked ? lockedLeafColumns?.length : 0"
@@ -32725,7 +35261,9 @@ class RowPinContainerComponent {
32725
35261
  [pinPosition]="position"
32726
35262
  [groups]="groups"
32727
35263
  [detailTemplate]="detailTemplate"
32728
- [filterable]="filterable">
35264
+ [filterable]="filterable"
35265
+ [rowHeight]="rowHeight"
35266
+ [detailRowHeight]="detailRowHeight">
32729
35267
  </tbody>
32730
35268
  </table>
32731
35269
  </div>
@@ -32738,9 +35276,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32738
35276
  template: `
32739
35277
  @if (isLocked) {
32740
35278
  <div class="k-grid-content-locked" role="presentation" [style.width.px]="lockedWidth">
32741
- <table class="k-grid-table"
35279
+ <table #lockedTable class="k-grid-table"
32742
35280
  kendoGridTable
32743
35281
  kendoGridResizableTable
35282
+ [locked]="true"
35283
+ [size]="size"
32744
35284
  [style.width.px]="lockedWidth">
32745
35285
  <colgroup kendoGridColGroup
32746
35286
  [groups]="groups"
@@ -32753,7 +35293,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32753
35293
  [isLocked]="true"
32754
35294
  [rowsToRender]="rowsToRender"
32755
35295
  [columns]="$any(lockedLeafColumns)"
32756
- [totalColumnsCount]="totalColumns.leafColumns.length"
35296
+ [totalColumnsCount]="leafColumns.length"
32757
35297
  [totalColumns]="totalColumns"
32758
35298
  [selectable]="selectable"
32759
35299
  [trackBy]="trackBy"
@@ -32761,13 +35301,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32761
35301
  [pinPosition]="position"
32762
35302
  [groups]="groups"
32763
35303
  [detailTemplate]="detailTemplate"
32764
- [filterable]="filterable">
35304
+ [filterable]="filterable"
35305
+ [rowHeight]="rowHeight"
35306
+ [detailRowHeight]="detailRowHeight"
35307
+ [isLoading]="loading">
32765
35308
  </tbody>
32766
35309
  </table>
32767
35310
  </div>
32768
35311
  }
32769
35312
  <div #pinnedWrapper class="k-grid-pinned-wrap">
32770
- <table class="k-grid-table"
35313
+ <table #pinnedTable class="k-grid-table"
32771
35314
  kendoGridTable
32772
35315
  kendoGridResizableTable
32773
35316
  [style.width.px]="nonLockedWidth"
@@ -32775,11 +35318,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32775
35318
  <colgroup kendoGridColGroup
32776
35319
  [columns]="$any(nonLockedLeafColumns)"
32777
35320
  [sort]="sort"
32778
- [groups]="groups"
35321
+ [groups]="isLocked ? [] : groups"
32779
35322
  [detailTemplate]="detailTemplate">
32780
35323
  </colgroup>
32781
35324
  <tbody kendoGridTableBody
32782
35325
  role="presentation"
35326
+ [skipGroupDecoration]="isLocked"
32783
35327
  [rowsToRender]="rowsToRender"
32784
35328
  [columns]="$any(nonLockedLeafColumns)"
32785
35329
  [lockedColumnsCount]="isLocked ? lockedLeafColumns?.length : 0"
@@ -32791,7 +35335,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32791
35335
  [pinPosition]="position"
32792
35336
  [groups]="groups"
32793
35337
  [detailTemplate]="detailTemplate"
32794
- [filterable]="filterable">
35338
+ [filterable]="filterable"
35339
+ [rowHeight]="rowHeight"
35340
+ [detailRowHeight]="detailRowHeight">
32795
35341
  </tbody>
32796
35342
  </table>
32797
35343
  </div>
@@ -32803,6 +35349,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32803
35349
  }], ctorParameters: () => [{ type: i1$2.LocalizationService }], propDecorators: { pinnedWrapper: [{
32804
35350
  type: ViewChild,
32805
35351
  args: ['pinnedWrapper', { static: true }]
35352
+ }], lockedTable: [{
35353
+ type: ViewChild,
35354
+ args: ['lockedTable']
35355
+ }], pinnedTable: [{
35356
+ type: ViewChild,
35357
+ args: ['pinnedTable', { static: true }]
32806
35358
  }], hostClass: [{
32807
35359
  type: HostBinding,
32808
35360
  args: ['class.k-grid-pinned-container']
@@ -32839,8 +35391,128 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
32839
35391
  type: Input
32840
35392
  }], filterable: [{
32841
35393
  type: Input
35394
+ }], rowHeight: [{
35395
+ type: Input
35396
+ }], detailRowHeight: [{
35397
+ type: Input
35398
+ }], loading: [{
35399
+ type: Input
35400
+ }], size: [{
35401
+ type: Input
32842
35402
  }] } });
32843
35403
 
35404
+ /**
35405
+ * Tracks the relationship between pinned rows and their counterparts in the main data table.
35406
+ * Use this service to find the main row index for a given pinned row data item,
35407
+ * or to find the pinned counterpart of a main row.
35408
+ *
35409
+ * The service is updated automatically by the Grid when `pinnedTopRows`, `pinnedBottomRows`,
35410
+ * or `data` changes. For reliable index resolution, pinned row data items must be
35411
+ * reference-equal to the corresponding items in the main data array.
35412
+ *
35413
+ * @hidden
35414
+ */
35415
+ class PinnedRowTrackingService {
35416
+ topIndexMap = new Map();
35417
+ bottomIndexMap = new Map();
35418
+ mainIndexMap = new Map();
35419
+ /**
35420
+ * Updates the internal tracking maps based on the current pinned rows and main data.
35421
+ * Called automatically by the Grid when relevant inputs change.
35422
+ *
35423
+ * @param topRows - The array of data items pinned to the top.
35424
+ * @param bottomRows - The array of data items pinned to the bottom.
35425
+ * @param data - The flat main data array for the current page.
35426
+ * @param skip - The current page skip offset used to compute absolute row indices.
35427
+ */
35428
+ update(topRows, bottomRows, data, skip) {
35429
+ this.topIndexMap.clear();
35430
+ this.bottomIndexMap.clear();
35431
+ this.mainIndexMap.clear();
35432
+ (topRows || []).forEach(dataItem => {
35433
+ const pageIdx = (data || []).indexOf(dataItem);
35434
+ if (pageIdx >= 0) {
35435
+ const mainIndex = skip + pageIdx;
35436
+ this.topIndexMap.set(dataItem, mainIndex);
35437
+ this.mainIndexMap.set(mainIndex, { dataItem, position: 'top' });
35438
+ }
35439
+ });
35440
+ (bottomRows || []).forEach(dataItem => {
35441
+ const pageIdx = (data || []).indexOf(dataItem);
35442
+ if (pageIdx >= 0) {
35443
+ const mainIndex = skip + pageIdx;
35444
+ this.bottomIndexMap.set(dataItem, mainIndex);
35445
+ this.mainIndexMap.set(mainIndex, { dataItem, position: 'bottom' });
35446
+ }
35447
+ });
35448
+ }
35449
+ /**
35450
+ * Updates the internal tracking maps using a pre-built data-item-to-index map from the main view.
35451
+ * This handles grouping, sorting, and pagination correctly.
35452
+ *
35453
+ * @param topRows - The array of data items pinned to the top.
35454
+ * @param bottomRows - The array of data items pinned to the bottom.
35455
+ * @param dataToIndex - A map from data item references to their absolute row indices in the main view.
35456
+ */
35457
+ updateFromIndexMap(topRows, bottomRows, dataToIndex) {
35458
+ this.topIndexMap.clear();
35459
+ this.bottomIndexMap.clear();
35460
+ this.mainIndexMap.clear();
35461
+ (topRows || []).forEach(dataItem => {
35462
+ const mainIndex = dataToIndex.get(dataItem);
35463
+ if (mainIndex !== undefined) {
35464
+ this.topIndexMap.set(dataItem, mainIndex);
35465
+ this.mainIndexMap.set(mainIndex, { dataItem, position: 'top' });
35466
+ }
35467
+ });
35468
+ (bottomRows || []).forEach(dataItem => {
35469
+ const mainIndex = dataToIndex.get(dataItem);
35470
+ if (mainIndex !== undefined) {
35471
+ this.bottomIndexMap.set(dataItem, mainIndex);
35472
+ this.mainIndexMap.set(mainIndex, { dataItem, position: 'bottom' });
35473
+ }
35474
+ });
35475
+ }
35476
+ /**
35477
+ * Returns the absolute main row index for a pinned row data item, or `-1` if not found.
35478
+ * The returned index matches the `index` property used by selection and editing services.
35479
+ */
35480
+ getMainRowIndex(dataItem) {
35481
+ return this.topIndexMap.get(dataItem) ?? this.bottomIndexMap.get(dataItem) ?? -1;
35482
+ }
35483
+ /**
35484
+ * Returns `true` if the given data item is currently pinned to the top or bottom.
35485
+ */
35486
+ isRowPinned(dataItem) {
35487
+ return this.topIndexMap.has(dataItem) || this.bottomIndexMap.has(dataItem);
35488
+ }
35489
+ /**
35490
+ * Returns the pin position (`'top'` or `'bottom'`) for the given data item,
35491
+ * or `null` if the item is not pinned.
35492
+ */
35493
+ getPinnedPosition(dataItem) {
35494
+ if (this.topIndexMap.has(dataItem)) {
35495
+ return 'top';
35496
+ }
35497
+ if (this.bottomIndexMap.has(dataItem)) {
35498
+ return 'bottom';
35499
+ }
35500
+ return null;
35501
+ }
35502
+ /**
35503
+ * Returns the pinned counterpart for a given main row index,
35504
+ * or `null` if the main row has no pinned counterpart.
35505
+ */
35506
+ getPinnedCounterpart(mainIndex) {
35507
+ return this.mainIndexMap.get(mainIndex) || null;
35508
+ }
35509
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PinnedRowTrackingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
35510
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PinnedRowTrackingService });
35511
+ }
35512
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PinnedRowTrackingService, decorators: [{
35513
+ type: Injectable
35514
+ }] });
35515
+
32844
35516
  const createControl = (source) => (acc, key) => {
32845
35517
  acc[key] = new FormControl(source[key]);
32846
35518
  return acc;
@@ -32928,6 +35600,7 @@ class GridComponent {
32928
35600
  idService;
32929
35601
  searchService;
32930
35602
  rowPinService;
35603
+ pinnedRowTrackingService;
32931
35604
  /**
32932
35605
  * Sets the data of the Grid. If you provide an array, the Grid gets the total count automatically.
32933
35606
  * ([more information and example](https://www.telerik.com/kendo-angular-ui/components/grid/data-binding/basics)).
@@ -33820,13 +36493,41 @@ class GridComponent {
33820
36493
  get flatData() {
33821
36494
  return isArray(this.data) ? this.data : this.data.data;
33822
36495
  }
36496
+ /**
36497
+ * @hidden
36498
+ */
36499
+ updatePinnedRowTracking() {
36500
+ const dataToIndex = this.buildViewIndexMap();
36501
+ this.pinnedRowTrackingService.updateFromIndexMap(this.pinnedTopRows, this.pinnedBottomRows, dataToIndex);
36502
+ }
36503
+ buildViewIndexMap() {
36504
+ const dataToIndex = new Map();
36505
+ const iter = this.view[iterator]();
36506
+ let next = iter.next();
36507
+ while (!next.done) {
36508
+ const viewItem = next.value;
36509
+ if (viewItem?.type === 'data') {
36510
+ dataToIndex.set(viewItem.data, viewItem.index);
36511
+ }
36512
+ next = iter.next();
36513
+ }
36514
+ return dataToIndex;
36515
+ }
33823
36516
  adjustPinnedItemIndexes(items) {
33824
- const flatData = this.flatData;
36517
+ const dataToIndex = this.buildViewIndexMap();
33825
36518
  items.forEach((item) => {
33826
36519
  if (item.type === 'data') {
33827
- const pageIdx = flatData.indexOf(item.data);
33828
- if (pageIdx >= 0) {
33829
- item.index = this.skip + pageIdx;
36520
+ // Preserve the render-order position (0, 1, 2...) for stable isOdd styling.
36521
+ item.pinnedIndex = item.index;
36522
+ const mainIndex = dataToIndex.get(item.data);
36523
+ if (mainIndex !== undefined) {
36524
+ item.index = mainIndex;
36525
+ }
36526
+ else {
36527
+ // Use a negative index when the pinned item is absent from the current view
36528
+ // (e.g. filtered out or on a different page) to avoid colliding with main
36529
+ // body row indices, which would cause incorrect selection state sync.
36530
+ item.index = -(item.pinnedIndex + 1);
33830
36531
  }
33831
36532
  }
33832
36533
  });
@@ -33992,7 +36693,7 @@ class GridComponent {
33992
36693
  pinnedContainersChangeSubscription;
33993
36694
  rtl = false;
33994
36695
  _rowSticky;
33995
- constructor(supportService, selectionService, cellSelectionService, wrapper, groupInfoService, groupsService, changeNotification, detailsService, editService, filterService, pdfService, responsiveService, renderer, excelService, csvService, ngZone, scrollSyncService, domEvents, columnResizingService, changeDetectorRef, columnReorderService, columnInfoService, navigationService, sortService, scrollRequestService, localization, ctx, sizingService, adaptiveGridService, rowReorderService, dataMappingService, aiRequestResponseService, idService, searchService, rowPinService) {
36696
+ constructor(supportService, selectionService, cellSelectionService, wrapper, groupInfoService, groupsService, changeNotification, detailsService, editService, filterService, pdfService, responsiveService, renderer, excelService, csvService, ngZone, scrollSyncService, domEvents, columnResizingService, changeDetectorRef, columnReorderService, columnInfoService, navigationService, sortService, scrollRequestService, localization, ctx, sizingService, adaptiveGridService, rowReorderService, dataMappingService, aiRequestResponseService, idService, searchService, rowPinService, pinnedRowTrackingService) {
33996
36697
  this.supportService = supportService;
33997
36698
  this.selectionService = selectionService;
33998
36699
  this.cellSelectionService = cellSelectionService;
@@ -34028,9 +36729,13 @@ class GridComponent {
34028
36729
  this.idService = idService;
34029
36730
  this.searchService = searchService;
34030
36731
  this.rowPinService = rowPinService;
36732
+ this.pinnedRowTrackingService = pinnedRowTrackingService;
34031
36733
  const isValid = validatePackage(packageMetadata);
34032
36734
  this.licenseMessage = getLicenseMessage(packageMetadata);
34033
36735
  this.showLicenseWatermark = shouldShowValidationUI(isValid);
36736
+ if (isDocumentAvailable()) {
36737
+ document.body.style.setProperty('--kendo-scrollbar-width', `${this.scrollbarWidth}px`);
36738
+ }
34034
36739
  this.ctx.grid = this;
34035
36740
  this.ctx.navigable = typeof this.navigable === 'boolean' ? this.navigable : this.navigable.includes('table');
34036
36741
  this.groupChange = new ZoneAwareEventEmitter(this.ngZone);
@@ -34185,6 +36890,7 @@ class GridComponent {
34185
36890
  }
34186
36891
  this.initSelectionService();
34187
36892
  this.updateNavigationMetadata();
36893
+ this.updatePinnedRowTracking();
34188
36894
  }
34189
36895
  ngDoCheck() {
34190
36896
  if (!this.isScrollable) {
@@ -34204,6 +36910,9 @@ class GridComponent {
34204
36910
  if (this.lockedLeafColumns.length && anyChanged(["pageSize", "skip", "sort", "group"], changes)) {
34205
36911
  this.changeNotification.notify();
34206
36912
  }
36913
+ if (isChanged$1("skip", changes)) {
36914
+ this.updatePinnedRowTracking();
36915
+ }
34207
36916
  if (anyChanged(["pageSize", "scrollable", 'virtualColumns'], changes)) {
34208
36917
  this.updateNavigationMetadata();
34209
36918
  }
@@ -34229,6 +36938,13 @@ class GridComponent {
34229
36938
  if (this.isPinnable) {
34230
36939
  this.ngZone.onStable.pipe(take(1)).subscribe(() => this.attachScrollSync());
34231
36940
  }
36941
+ this.updatePinnedRowTracking();
36942
+ // Pinned container height changes affect the scrollable viewport size
36943
+ if (this.isVirtual && this.listComponent && (isChanged$1("pinnedTopRows", changes) || isChanged$1("pinnedBottomRows", changes))) {
36944
+ this.ngZone.onStable.pipe(take(1)).subscribe(() => {
36945
+ this.listComponent.onPinnedContainerHeightChange();
36946
+ });
36947
+ }
34232
36948
  }
34233
36949
  if (isChanged$1("selectable", changes) && this.shouldResetSelection(changes['selectable'])) {
34234
36950
  if (this.defaultSelection) {
@@ -35407,7 +38123,7 @@ class GridComponent {
35407
38123
  this.dragTargetContainer?.notify();
35408
38124
  this.dropTargetContainer?.notify();
35409
38125
  }
35410
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GridComponent, deps: [{ token: i1$3.ScrollbarService }, { token: SelectionService }, { token: CellSelectionService }, { token: i0.ElementRef }, { token: GroupInfoService }, { token: GroupsService }, { token: ChangeNotificationService }, { token: DetailsService }, { token: EditService }, { token: FilterService }, { token: PDFService }, { token: ResponsiveService }, { token: i0.Renderer2 }, { token: ExcelService }, { token: CSVService }, { token: i0.NgZone }, { token: ScrollSyncService }, { token: DomEventsService }, { token: ColumnResizingService }, { token: i0.ChangeDetectorRef }, { token: ColumnReorderService }, { token: ColumnInfoService }, { token: NavigationService }, { token: SortService }, { token: ScrollRequestService }, { token: i1$2.LocalizationService }, { token: ContextService }, { token: SizingOptionsService }, { token: AdaptiveGridService }, { token: RowReorderService }, { token: DataMappingService }, { token: GridAIRequestResponseService }, { token: IdService }, { token: SearchService }, { token: RowPinService }], target: i0.ɵɵFactoryTarget.Component });
38126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GridComponent, deps: [{ token: i1$3.ScrollbarService }, { token: SelectionService }, { token: CellSelectionService }, { token: i0.ElementRef }, { token: GroupInfoService }, { token: GroupsService }, { token: ChangeNotificationService }, { token: DetailsService }, { token: EditService }, { token: FilterService }, { token: PDFService }, { token: ResponsiveService }, { token: i0.Renderer2 }, { token: ExcelService }, { token: CSVService }, { token: i0.NgZone }, { token: ScrollSyncService }, { token: DomEventsService }, { token: ColumnResizingService }, { token: i0.ChangeDetectorRef }, { token: ColumnReorderService }, { token: ColumnInfoService }, { token: NavigationService }, { token: SortService }, { token: ScrollRequestService }, { token: i1$2.LocalizationService }, { token: ContextService }, { token: SizingOptionsService }, { token: AdaptiveGridService }, { token: RowReorderService }, { token: DataMappingService }, { token: GridAIRequestResponseService }, { token: IdService }, { token: SearchService }, { token: RowPinService }, { token: PinnedRowTrackingService }], target: i0.ɵɵFactoryTarget.Component });
35411
38127
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: GridComponent, isStandalone: true, selector: "kendo-grid", inputs: { data: "data", pageSize: "pageSize", height: "height", rowHeight: "rowHeight", adaptiveMode: "adaptiveMode", detailRowHeight: "detailRowHeight", skip: "skip", scrollable: "scrollable", selectable: "selectable", sort: "sort", size: "size", trackBy: "trackBy", filter: "filter", group: "group", virtualColumns: "virtualColumns", filterable: "filterable", sortable: "sortable", pageable: "pageable", groupable: "groupable", gridResizable: "gridResizable", rowReorderable: "rowReorderable", navigable: "navigable", autoSize: "autoSize", rowClass: "rowClass", rowSticky: "rowSticky", rowSelected: "rowSelected", isRowSelectable: "isRowSelectable", cellSelected: "cellSelected", resizable: "resizable", reorderable: "reorderable", loading: "loading", columnMenu: "columnMenu", hideHeader: "hideHeader", showInactiveTools: "showInactiveTools", isDetailExpanded: "isDetailExpanded", isGroupExpanded: "isGroupExpanded", dataLayoutMode: "dataLayoutMode", pinnable: "pinnable", pinnedTopRows: "pinnedTopRows", pinnedBottomRows: "pinnedBottomRows", isRowPinnable: "isRowPinnable" }, outputs: { filterChange: "filterChange", pageChange: "pageChange", groupChange: "groupChange", sortChange: "sortChange", selectionChange: "selectionChange", rowReorder: "rowReorder", rowPinChange: "rowPinChange", dataStateChange: "dataStateChange", gridStateChange: "gridStateChange", groupExpand: "groupExpand", groupCollapse: "groupCollapse", detailExpand: "detailExpand", detailCollapse: "detailCollapse", edit: "edit", cancel: "cancel", save: "save", remove: "remove", add: "add", cellClose: "cellClose", cellClick: "cellClick", pdfExport: "pdfExport", excelExport: "excelExport", csvExport: "csvExport", columnResize: "columnResize", columnReorder: "columnReorder", columnVisibilityChange: "columnVisibilityChange", columnLockedChange: "columnLockedChange", columnStickyChange: "columnStickyChange", scrollBottom: "scrollBottom", contentScroll: "contentScroll" }, host: { properties: { "attr.dir": "this.dir", "class.k-grid": "this.hostClass", "class.k-grid-sm": "this.sizeSmallClass", "class.k-grid-md": "this.sizeMediumClass", "class.k-grid-stack": "this.stackedClass", "class.k-grid-lockedcolumns": "this.lockedClasses", "class.k-grid-virtual": "this.virtualClasses", "class.k-grid-no-scrollbar": "this.noScrollbarClass", "class.k-grid-resizable": "this.isResizable", "style.minWidth": "this.minWidth", "style.maxWidth": "this.maxWidth", "style.minHeight": "this.minHeight", "style.maxHeight": "this.maxHeight" } }, providers: [
35412
38128
  LocalizationService,
35413
38129
  ColumnInfoService,
@@ -35461,7 +38177,9 @@ class GridComponent {
35461
38177
  MenuTabbingService,
35462
38178
  DataMappingService,
35463
38179
  SearchService,
35464
- RowPinService
38180
+ RowPinService,
38181
+ StickyGroupsService,
38182
+ PinnedRowTrackingService
35465
38183
  ], queries: [{ propertyName: "columns", predicate: ColumnBase }, { propertyName: "detailTemplateChildren", predicate: DetailTemplateDirective }, { propertyName: "cellLoadingTemplateChildren", predicate: CellLoadingTemplateDirective }, { propertyName: "loadingTemplateChildren", predicate: LoadingTemplateDirective }, { propertyName: "statusBarTemplateChildren", predicate: StatusBarTemplateDirective }, { propertyName: "noRecordsTemplateChildren", predicate: NoRecordsTemplateDirective }, { propertyName: "pagerTemplateChildren", predicate: PagerTemplateDirective }, { propertyName: "toolbarTemplateChildren", predicate: ToolbarTemplateDirective }, { propertyName: "columnMenuTemplates", predicate: ColumnMenuTemplateDirective }], viewQueries: [{ propertyName: "lockedHeader", first: true, predicate: ["lockedHeader"], descendants: true }, { propertyName: "header", first: true, predicate: ["header"], descendants: true }, { propertyName: "ariaRoot", first: true, predicate: ["ariaRoot"], descendants: true, static: true }, { propertyName: "dragTargetContainer", first: true, predicate: DragTargetContainerDirective, descendants: true }, { propertyName: "dropTargetContainer", first: true, predicate: DropTargetContainerDirective, descendants: true }, { propertyName: "dialogContainer", first: true, predicate: ["dialogContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "windowContainer", first: true, predicate: ["windowContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "adaptiveRenderer", first: true, predicate: AdaptiveRendererComponent, descendants: true }, { propertyName: "listComponent", first: true, predicate: ListComponent, descendants: true }, { propertyName: "pinnedContainers", predicate: RowPinContainerComponent, descendants: true }, { propertyName: "footer", predicate: ["footer"], descendants: true }], exportAs: ["kendoGrid"], usesOnChanges: true, ngImport: i0, template: `
35466
38184
  <ng-container kendoGridLocalizedMessages
35467
38185
  i18n-groupPanelEmpty="kendo.grid.groupPanelEmpty|The label visible in the Grid group panel when it is empty"
@@ -35943,6 +38661,12 @@ class GridComponent {
35943
38661
 
35944
38662
  i18n-pinColumnHeaderLabel="kendo.grid.pinColumnHeaderLabel|The screen-reader-only content for the pin column header"
35945
38663
  pinColumnHeaderLabel="Actions"
38664
+
38665
+ i18n-rowPinLabel="kendo.grid.rowPinLabel|Sets the label for the Grid row pin column icon"
38666
+ rowPinLabel="Pin row"
38667
+
38668
+ i18n-rowUnpinLabel="kendo.grid.rowUnpinLabel|Sets the label for the Grid row unpin column icon"
38669
+ rowUnpinLabel="Unpin row"
35946
38670
  >
35947
38671
  </ng-container>
35948
38672
  @if (showTopToolbar) {
@@ -36142,7 +38866,9 @@ class GridComponent {
36142
38866
  [trackBy]="trackBy"
36143
38867
  [groups]="group"
36144
38868
  [detailTemplate]="detailTemplate"
36145
- [filterable]="filterable">
38869
+ [filterable]="filterable"
38870
+ [rowHeight]="rowHeight"
38871
+ [detailRowHeight]="detailRowHeight">
36146
38872
  </kendo-grid-rowpin-container>
36147
38873
  }
36148
38874
 
@@ -36194,7 +38920,9 @@ class GridComponent {
36194
38920
  [trackBy]="trackBy"
36195
38921
  [groups]="group"
36196
38922
  [detailTemplate]="detailTemplate"
36197
- [filterable]="filterable">
38923
+ [filterable]="filterable"
38924
+ [rowHeight]="rowHeight"
38925
+ [detailRowHeight]="detailRowHeight">
36198
38926
  </kendo-grid-rowpin-container>
36199
38927
  }
36200
38928
  @if (showFooter) {
@@ -36282,7 +39010,9 @@ class GridComponent {
36282
39010
  [trackBy]="trackBy"
36283
39011
  [groups]="group"
36284
39012
  [detailTemplate]="detailTemplate"
36285
- [filterable]="filterable">
39013
+ [filterable]="filterable"
39014
+ [rowHeight]="rowHeight"
39015
+ [detailRowHeight]="detailRowHeight">
36286
39016
  </kendo-grid-rowpin-container>
36287
39017
  }
36288
39018
  <table
@@ -36369,7 +39099,11 @@ class GridComponent {
36369
39099
  [trackBy]="trackBy"
36370
39100
  [groups]="group"
36371
39101
  [detailTemplate]="detailTemplate"
36372
- [filterable]="filterable">
39102
+ [filterable]="filterable"
39103
+ [rowHeight]="rowHeight"
39104
+ [detailRowHeight]="detailRowHeight"
39105
+ [size]="size"
39106
+ [loading]="loading">
36373
39107
  </kendo-grid-rowpin-container>
36374
39108
  }
36375
39109
  }
@@ -36487,7 +39221,7 @@ class GridComponent {
36487
39221
  @if (showLicenseWatermark) {
36488
39222
  <div kendoWatermarkOverlay [licenseMessage]="licenseMessage"></div>
36489
39223
  }
36490
- `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective, selector: "[kendoGridLocalizedMessages]" }, { kind: "component", type: ToolbarComponent, selector: "kendo-grid-toolbar", inputs: ["position", "size", "navigable"] }, { kind: "component", type: GroupPanelComponent, selector: "kendo-grid-group-panel", inputs: ["text", "navigable", "groups"], outputs: ["change"] }, { kind: "directive", type: TableDirective, selector: "[kendoGridResizableTable]", inputs: ["locked", "virtualColumns"] }, { kind: "directive", type: GridTableDirective, selector: "[kendoGridTable]", inputs: ["size"] }, { kind: "component", type: ColGroupComponent, selector: "[kendoGridColGroup]", inputs: ["columns", "groups", "detailTemplate", "sort"] }, { kind: "component", type: HeaderComponent, selector: "[kendoGridHeader]", inputs: ["totalColumnLevels", "columns", "groups", "detailTemplate", "scrollable", "filterable", "sort", "filter", "sortable", "groupable", "lockedColumnsCount", "resizable", "reorderable", "columnMenu", "columnMenuTemplate", "totalColumnsCount", "totalColumns", "tabIndex", "size"] }, { kind: "directive", type: ResizableContainerDirective, selector: "[kendoGridResizableContainer]", inputs: ["lockedWidth", "kendoGridResizableContainer"] }, { kind: "component", type: ListComponent, selector: "kendo-grid-list", inputs: ["data", "groups", "total", "rowHeight", "detailRowHeight", "take", "skip", "columns", "detailTemplate", "noRecordsTemplate", "selectable", "groupable", "filterable", "rowClass", "rowSticky", "loading", "trackBy", "virtualColumns", "isVirtual", "cellLoadingTemplate", "loadingTemplate", "sort", "size"], outputs: ["contentScroll", "pageChange", "scrollBottom"] }, { kind: "directive", type: DragTargetContainerDirective, selector: "[kendoDragTargetContainer]", inputs: ["hint", "dragTargetFilter", "dragHandle", "dragDelay", "threshold", "dragTargetId", "dragData", "dragDisabled", "mode", "cursorStyle", "hintContext"], outputs: ["onDragReady", "onPress", "onDragStart", "onDrag", "onRelease", "onDragEnd"], exportAs: ["kendoDragTargetContainer"] }, { kind: "directive", type: DropTargetContainerDirective, selector: "[kendoDropTargetContainer]", inputs: ["dropTargetFilter", "dropDisabled"], outputs: ["onDragEnter", "onDragOver", "onDragLeave", "onDrop"], exportAs: ["kendoDropTargetContainer"] }, { kind: "directive", type: DraggableDirective, selector: "[kendoDraggable]", inputs: ["enableDrag"], outputs: ["kendoPress", "kendoDrag", "kendoRelease"] }, { kind: "directive", type: GridMarqueeDirective, selector: "[kendoGridSelectionMarquee]" }, { kind: "component", type: FooterComponent, selector: "[kendoGridFooter]", inputs: ["columns", "groups", "detailTemplate", "scrollable", "lockedColumnsCount", "logicalRowIndex", "totalColumns", "totalColumnsCount"] }, { kind: "component", type: TableBodyComponent, selector: "[kendoGridTableBody]", inputs: ["columns", "allColumns", "groups", "detailTemplate", "noRecordsTemplate", "rowsToRender", "skip", "selectable", "filterable", "noRecordsText", "isLocked", "isLoading", "isVirtual", "cellLoadingTemplate", "skipGroupDecoration", "lockedColumnsCount", "totalColumnsCount", "virtualColumns", "trackBy", "rowSticky", "totalColumns", "rowClass", "rowHeight", "detailRowHeight", "isPinContainer", "pinPosition"] }, { kind: "component", type: LoadingComponent, selector: "[kendoGridLoading]", inputs: ["loadingTemplate"] }, { kind: "component", type: StatusBarComponent, selector: "kendo-grid-status-bar", inputs: ["statusBarTemplate"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: WatermarkOverlayComponent, selector: "div[kendoWatermarkOverlay], kendo-watermark-overlay", inputs: ["licenseMessage"] }, { kind: "component", type: i57.CustomMessagesComponent, selector: "kendo-datapager-messages, kendo-pager-messages" }, { kind: "component", type: i57.PagerInfoComponent, selector: "kendo-datapager-info, kendo-pager-info" }, { kind: "component", type: i57.PagerInputComponent, selector: "kendo-datapager-input, kendo-pager-input", inputs: ["showPageText", "size"] }, { kind: "component", type: i57.PagerNextButtonsComponent, selector: "kendo-datapager-next-buttons, kendo-pager-next-buttons", inputs: ["size"] }, { kind: "component", type: i57.PagerNumericButtonsComponent, selector: "kendo-datapager-numeric-buttons, kendo-pager-numeric-buttons", inputs: ["buttonCount", "size"] }, { kind: "component", type: i57.PagerPageSizesComponent, selector: "kendo-datapager-page-sizes, kendo-pager-page-sizes", inputs: ["showItemsText", "pageSizes", "size", "adaptiveMode"] }, { kind: "component", type: i57.PagerPrevButtonsComponent, selector: "kendo-datapager-prev-buttons, kendo-pager-prev-buttons", inputs: ["size"] }, { kind: "directive", type: i57.PagerTemplateDirective, selector: "[kendoDataPagerTemplate], [kendoPagerTemplate]" }, { kind: "component", type: i57.PagerComponent, selector: "kendo-datapager, kendo-pager", inputs: ["externalTemplate", "total", "skip", "pageSize", "buttonCount", "info", "type", "pageSizeValues", "previousNext", "navigable", "size", "responsive", "adaptiveMode"], outputs: ["pageChange", "pageSizeChange", "pagerInputVisibilityChange", "pageTextVisibilityChange", "itemsTextVisibilityChange"], exportAs: ["kendoDataPager", "kendoPager"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AdaptiveRendererComponent, selector: "kendo-grid-adaptive-renderer" }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }, { kind: "component", type: RowPinContainerComponent, selector: "kendo-grid-rowpin-container", inputs: ["leafColumns", "lockedLeafColumns", "nonLockedLeafColumns", "sort", "position", "rowsToRender", "totalColumns", "lockedWidth", "nonLockedWidth", "isLocked", "selectable", "trackBy", "groups", "detailTemplate", "filterable"] }], encapsulation: i0.ViewEncapsulation.None });
39224
+ `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective, selector: "[kendoGridLocalizedMessages]" }, { kind: "component", type: ToolbarComponent, selector: "kendo-grid-toolbar", inputs: ["position", "size", "navigable"] }, { kind: "component", type: GroupPanelComponent, selector: "kendo-grid-group-panel", inputs: ["text", "navigable", "groups"], outputs: ["change"] }, { kind: "directive", type: TableDirective, selector: "[kendoGridResizableTable]", inputs: ["locked", "virtualColumns"] }, { kind: "directive", type: GridTableDirective, selector: "[kendoGridTable]", inputs: ["size"] }, { kind: "component", type: ColGroupComponent, selector: "[kendoGridColGroup]", inputs: ["columns", "groups", "detailTemplate", "sort"] }, { kind: "component", type: HeaderComponent, selector: "[kendoGridHeader]", inputs: ["totalColumnLevels", "columns", "groups", "detailTemplate", "scrollable", "filterable", "sort", "filter", "sortable", "groupable", "lockedColumnsCount", "resizable", "reorderable", "columnMenu", "columnMenuTemplate", "totalColumnsCount", "totalColumns", "tabIndex", "size"] }, { kind: "directive", type: ResizableContainerDirective, selector: "[kendoGridResizableContainer]", inputs: ["lockedWidth", "kendoGridResizableContainer"] }, { kind: "component", type: ListComponent, selector: "kendo-grid-list", inputs: ["data", "groups", "total", "rowHeight", "detailRowHeight", "take", "skip", "columns", "detailTemplate", "noRecordsTemplate", "selectable", "groupable", "filterable", "rowClass", "rowSticky", "loading", "trackBy", "virtualColumns", "isVirtual", "cellLoadingTemplate", "loadingTemplate", "sort", "size"], outputs: ["contentScroll", "pageChange", "scrollBottom"] }, { kind: "directive", type: DragTargetContainerDirective, selector: "[kendoDragTargetContainer]", inputs: ["hint", "dragTargetFilter", "dragHandle", "dragDelay", "threshold", "dragTargetId", "dragData", "dragDisabled", "mode", "cursorStyle", "hintContext"], outputs: ["onDragReady", "onPress", "onDragStart", "onDrag", "onRelease", "onDragEnd"], exportAs: ["kendoDragTargetContainer"] }, { kind: "directive", type: DropTargetContainerDirective, selector: "[kendoDropTargetContainer]", inputs: ["dropTargetFilter", "dropDisabled"], outputs: ["onDragEnter", "onDragOver", "onDragLeave", "onDrop"], exportAs: ["kendoDropTargetContainer"] }, { kind: "directive", type: DraggableDirective, selector: "[kendoDraggable]", inputs: ["enableDrag"], outputs: ["kendoPress", "kendoDrag", "kendoRelease"] }, { kind: "directive", type: GridMarqueeDirective, selector: "[kendoGridSelectionMarquee]" }, { kind: "component", type: FooterComponent, selector: "[kendoGridFooter]", inputs: ["columns", "groups", "detailTemplate", "scrollable", "lockedColumnsCount", "logicalRowIndex", "totalColumns", "totalColumnsCount"] }, { kind: "component", type: TableBodyComponent, selector: "[kendoGridTableBody]", inputs: ["columns", "allColumns", "groups", "detailTemplate", "noRecordsTemplate", "rowsToRender", "skip", "selectable", "filterable", "noRecordsText", "isLocked", "isLoading", "isVirtual", "cellLoadingTemplate", "skipGroupDecoration", "lockedColumnsCount", "totalColumnsCount", "virtualColumns", "trackBy", "rowSticky", "totalColumns", "rowClass", "rowHeight", "detailRowHeight", "isPinContainer", "pinPosition"] }, { kind: "component", type: LoadingComponent, selector: "[kendoGridLoading]", inputs: ["loadingTemplate"] }, { kind: "component", type: StatusBarComponent, selector: "kendo-grid-status-bar", inputs: ["statusBarTemplate"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: WatermarkOverlayComponent, selector: "div[kendoWatermarkOverlay], kendo-watermark-overlay", inputs: ["licenseMessage"] }, { kind: "component", type: i57.CustomMessagesComponent, selector: "kendo-datapager-messages, kendo-pager-messages" }, { kind: "component", type: i57.PagerInfoComponent, selector: "kendo-datapager-info, kendo-pager-info" }, { kind: "component", type: i57.PagerInputComponent, selector: "kendo-datapager-input, kendo-pager-input", inputs: ["showPageText", "size"] }, { kind: "component", type: i57.PagerNextButtonsComponent, selector: "kendo-datapager-next-buttons, kendo-pager-next-buttons", inputs: ["size"] }, { kind: "component", type: i57.PagerNumericButtonsComponent, selector: "kendo-datapager-numeric-buttons, kendo-pager-numeric-buttons", inputs: ["buttonCount", "size"] }, { kind: "component", type: i57.PagerPageSizesComponent, selector: "kendo-datapager-page-sizes, kendo-pager-page-sizes", inputs: ["showItemsText", "pageSizes", "size", "adaptiveMode"] }, { kind: "component", type: i57.PagerPrevButtonsComponent, selector: "kendo-datapager-prev-buttons, kendo-pager-prev-buttons", inputs: ["size"] }, { kind: "directive", type: i57.PagerTemplateDirective, selector: "[kendoDataPagerTemplate], [kendoPagerTemplate]" }, { kind: "component", type: i57.PagerComponent, selector: "kendo-datapager, kendo-pager", inputs: ["externalTemplate", "total", "skip", "pageSize", "buttonCount", "info", "type", "pageSizeValues", "previousNext", "navigable", "size", "responsive", "adaptiveMode"], outputs: ["pageChange", "pageSizeChange", "pagerInputVisibilityChange", "pageTextVisibilityChange", "itemsTextVisibilityChange"], exportAs: ["kendoDataPager", "kendoPager"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AdaptiveRendererComponent, selector: "kendo-grid-adaptive-renderer" }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }, { kind: "component", type: RowPinContainerComponent, selector: "kendo-grid-rowpin-container", inputs: ["leafColumns", "lockedLeafColumns", "nonLockedLeafColumns", "sort", "position", "rowsToRender", "totalColumns", "lockedWidth", "nonLockedWidth", "isLocked", "selectable", "trackBy", "groups", "detailTemplate", "filterable", "rowHeight", "detailRowHeight", "loading", "size"] }], encapsulation: i0.ViewEncapsulation.None });
36491
39225
  }
36492
39226
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GridComponent, decorators: [{
36493
39227
  type: Component,
@@ -36547,7 +39281,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
36547
39281
  MenuTabbingService,
36548
39282
  DataMappingService,
36549
39283
  SearchService,
36550
- RowPinService
39284
+ RowPinService,
39285
+ StickyGroupsService,
39286
+ PinnedRowTrackingService
36551
39287
  ],
36552
39288
  selector: 'kendo-grid',
36553
39289
  template: `
@@ -37031,6 +39767,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
37031
39767
 
37032
39768
  i18n-pinColumnHeaderLabel="kendo.grid.pinColumnHeaderLabel|The screen-reader-only content for the pin column header"
37033
39769
  pinColumnHeaderLabel="Actions"
39770
+
39771
+ i18n-rowPinLabel="kendo.grid.rowPinLabel|Sets the label for the Grid row pin column icon"
39772
+ rowPinLabel="Pin row"
39773
+
39774
+ i18n-rowUnpinLabel="kendo.grid.rowUnpinLabel|Sets the label for the Grid row unpin column icon"
39775
+ rowUnpinLabel="Unpin row"
37034
39776
  >
37035
39777
  </ng-container>
37036
39778
  @if (showTopToolbar) {
@@ -37230,7 +39972,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
37230
39972
  [trackBy]="trackBy"
37231
39973
  [groups]="group"
37232
39974
  [detailTemplate]="detailTemplate"
37233
- [filterable]="filterable">
39975
+ [filterable]="filterable"
39976
+ [rowHeight]="rowHeight"
39977
+ [detailRowHeight]="detailRowHeight">
37234
39978
  </kendo-grid-rowpin-container>
37235
39979
  }
37236
39980
 
@@ -37282,7 +40026,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
37282
40026
  [trackBy]="trackBy"
37283
40027
  [groups]="group"
37284
40028
  [detailTemplate]="detailTemplate"
37285
- [filterable]="filterable">
40029
+ [filterable]="filterable"
40030
+ [rowHeight]="rowHeight"
40031
+ [detailRowHeight]="detailRowHeight">
37286
40032
  </kendo-grid-rowpin-container>
37287
40033
  }
37288
40034
  @if (showFooter) {
@@ -37370,7 +40116,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
37370
40116
  [trackBy]="trackBy"
37371
40117
  [groups]="group"
37372
40118
  [detailTemplate]="detailTemplate"
37373
- [filterable]="filterable">
40119
+ [filterable]="filterable"
40120
+ [rowHeight]="rowHeight"
40121
+ [detailRowHeight]="detailRowHeight">
37374
40122
  </kendo-grid-rowpin-container>
37375
40123
  }
37376
40124
  <table
@@ -37457,7 +40205,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
37457
40205
  [trackBy]="trackBy"
37458
40206
  [groups]="group"
37459
40207
  [detailTemplate]="detailTemplate"
37460
- [filterable]="filterable">
40208
+ [filterable]="filterable"
40209
+ [rowHeight]="rowHeight"
40210
+ [detailRowHeight]="detailRowHeight"
40211
+ [size]="size"
40212
+ [loading]="loading">
37461
40213
  </kendo-grid-rowpin-container>
37462
40214
  }
37463
40215
  }
@@ -37604,7 +40356,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
37604
40356
  RowPinContainerComponent
37605
40357
  ]
37606
40358
  }]
37607
- }], ctorParameters: () => [{ type: i1$3.ScrollbarService }, { type: SelectionService }, { type: CellSelectionService }, { type: i0.ElementRef }, { type: GroupInfoService }, { type: GroupsService }, { type: ChangeNotificationService }, { type: DetailsService }, { type: EditService }, { type: FilterService }, { type: PDFService }, { type: ResponsiveService }, { type: i0.Renderer2 }, { type: ExcelService }, { type: CSVService }, { type: i0.NgZone }, { type: ScrollSyncService }, { type: DomEventsService }, { type: ColumnResizingService }, { type: i0.ChangeDetectorRef }, { type: ColumnReorderService }, { type: ColumnInfoService }, { type: NavigationService }, { type: SortService }, { type: ScrollRequestService }, { type: i1$2.LocalizationService }, { type: ContextService }, { type: SizingOptionsService }, { type: AdaptiveGridService }, { type: RowReorderService }, { type: DataMappingService }, { type: GridAIRequestResponseService }, { type: IdService }, { type: SearchService }, { type: RowPinService }], propDecorators: { data: [{
40359
+ }], ctorParameters: () => [{ type: i1$3.ScrollbarService }, { type: SelectionService }, { type: CellSelectionService }, { type: i0.ElementRef }, { type: GroupInfoService }, { type: GroupsService }, { type: ChangeNotificationService }, { type: DetailsService }, { type: EditService }, { type: FilterService }, { type: PDFService }, { type: ResponsiveService }, { type: i0.Renderer2 }, { type: ExcelService }, { type: CSVService }, { type: i0.NgZone }, { type: ScrollSyncService }, { type: DomEventsService }, { type: ColumnResizingService }, { type: i0.ChangeDetectorRef }, { type: ColumnReorderService }, { type: ColumnInfoService }, { type: NavigationService }, { type: SortService }, { type: ScrollRequestService }, { type: i1$2.LocalizationService }, { type: ContextService }, { type: SizingOptionsService }, { type: AdaptiveGridService }, { type: RowReorderService }, { type: DataMappingService }, { type: GridAIRequestResponseService }, { type: IdService }, { type: SearchService }, { type: RowPinService }, { type: PinnedRowTrackingService }], propDecorators: { data: [{
37608
40360
  type: Input
37609
40361
  }], pageSize: [{
37610
40362
  type: Input
@@ -38063,6 +40815,9 @@ class DataBindingDirective {
38063
40815
  */
38064
40816
  onRowReorder(ev) {
38065
40817
  this.rowReorderService.reorderRows(ev, this.originalData);
40818
+ if (this.grid.pinnable !== false) {
40819
+ this.reorderPinnedRows(ev);
40820
+ }
38066
40821
  this.rebind();
38067
40822
  }
38068
40823
  /**
@@ -38190,6 +40945,18 @@ class DataBindingDirective {
38190
40945
  }
38191
40946
  return process(data, { sort: this.state.sort }).data;
38192
40947
  }
40948
+ reorderPinnedRows(ev) {
40949
+ if (this.grid.pinnedTopRows?.length) {
40950
+ if (this.grid.pinnedTopRows.some(row => row === ev.dropTargetRow?.dataItem)) {
40951
+ this.rowReorderService.reorderRows(ev, this.grid.pinnedTopRows);
40952
+ }
40953
+ }
40954
+ if (this.grid.pinnedBottomRows?.length) {
40955
+ if (this.grid.pinnedBottomRows.some(row => row === ev.dropTargetRow?.dataItem)) {
40956
+ this.rowReorderService.reorderRows(ev, this.grid.pinnedBottomRows);
40957
+ }
40958
+ }
40959
+ }
38193
40960
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataBindingDirective, deps: [{ token: GridComponent }, { token: i0.ChangeDetectorRef }, { token: LocalDataChangesService }, { token: RowReorderService }, { token: SearchService }, { token: RowPinService }, { token: ContextService }], target: i0.ɵɵFactoryTarget.Directive });
38194
40961
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: DataBindingDirective, isStandalone: true, selector: "[kendoGridBinding]", inputs: { skip: "skip", sort: "sort", filter: "filter", pageSize: "pageSize", group: "group", data: ["kendoGridBinding", "data"] }, exportAs: ["kendoGridBinding"], usesOnChanges: true, ngImport: i0 });
38195
40962
  }
@@ -38287,11 +41054,20 @@ class LocalEditService {
38287
41054
  for (let idx = 0; idx < data.length; idx++) {
38288
41055
  if (item === data[idx]) {
38289
41056
  data.splice(idx, 1);
41057
+ this.removePinnedRow(item);
38290
41058
  this.dataChanged({ action: 'remove', item: item });
38291
41059
  break;
38292
41060
  }
38293
41061
  }
38294
41062
  }
41063
+ removePinnedRow(item) {
41064
+ if (Array.isArray(this.grid.pinnedTopRows) && this.grid.pinnedTopRows.includes(item)) {
41065
+ this.grid.pinnedTopRows = this.grid.pinnedTopRows.filter((r) => r !== item);
41066
+ }
41067
+ if (Array.isArray(this.grid.pinnedBottomRows) && this.grid.pinnedBottomRows.includes(item)) {
41068
+ this.grid.pinnedBottomRows = this.grid.pinnedBottomRows.filter((r) => r !== item);
41069
+ }
41070
+ }
38295
41071
  assignValues(target, source) {
38296
41072
  Object.assign(target, source);
38297
41073
  }