@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
452 lines (447 loc) • 19.4 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Component, ElementRef, EventEmitter, HostBinding, Output, Input, HostListener, ViewChildren, QueryList, NgZone } from '@angular/core';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { chevronUpIcon, chevronDownIcon, xCircleIcon, plusCircleIcon, xIcon } from '@progress/kendo-svg-icons';
import { KENDO_BUTTON } from '@progress/kendo-angular-buttons';
import { take } from 'rxjs/operators';
import { isPresent, Keys, normalizeKeys } from '@progress/kendo-angular-common';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-buttons";
/**
* @hidden
*/
export class GroupToolbarToolComponent {
element;
ngZone;
hostClass = true;
get lgClass() {
return this.adaptive;
}
get mdClass() {
return !this.adaptive;
}
onEscKeyDown(event) {
event.preventDefault();
this.hostButton?.focus(event);
this.close.emit();
}
set groupItems(items) {
this._groupItems = items;
if (items?.first && (!isPresent(this.currentFocusedItemIndex) || this.currentFocusedItemIndex >= items.length || this.currentFocusedItemIndex < 0)) {
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
this.currentFocusedItemIndex = 0;
this.groupItems.first.nativeElement.focus({ preventScroll: true });
});
return;
}
if (items?.first) {
items.get(this.currentFocusedItemIndex).nativeElement.focus({ preventScroll: true });
}
}
get groupItems() {
return this._groupItems;
}
_groupItems;
adaptive = false;
close = new EventEmitter();
groupClear = new EventEmitter();
currentFocusedItemIndex;
group = new Array();
columns = [];
iconSize = 'medium';
upIcon = chevronUpIcon;
downIcon = chevronDownIcon;
removeIcon = xCircleIcon;
addIcon = plusCircleIcon;
clearIcon = xIcon;
_ctx;
set ctx(ctx) {
if (!ctx || !ctx.grid) {
return;
}
this._ctx = ctx;
this.group = ctx.grid.group;
this.subscription = ctx.grid.groupChange.subscribe((group) => {
this.group = group;
this.updateGroupedColumns();
});
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
this.updateGroupedColumns();
});
}
get ctx() {
return this._ctx;
}
groupedColumns = [];
ungroupedColumns = [];
subscription;
hostButton;
constructor(element, ngZone) {
this.element = element;
this.ngZone = ngZone;
}
ngOnInit() {
this.iconSize = this.adaptive ? 'large' : 'medium';
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
addGroup(column, ev) {
ev.stopImmediatePropagation();
const index = this.group.length;
const groups = this.group.filter(x => x.field !== column?.field);
if (groups.length || this.group.length === 0) {
this.group = [...groups.slice(0, index), { field: column?.field }, ...groups.slice(index)];
this.ctx.grid.groupChange.emit(this.group);
this.updateGroupedColumns();
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
const newIndex = this.groupedColumns.length - 1;
const newItem = this.groupItems.get(newIndex);
if (newItem) {
this.currentFocusedItemIndex = (this.groupedColumns?.length || 0) + newIndex;
newItem.nativeElement.focus();
}
});
}
}
removeGroup(column, ev) {
ev.stopImmediatePropagation();
this.group = this.group.filter(x => x.field !== column?.field);
this.ctx.grid.groupChange.emit(this.group);
this.updateGroupedColumns();
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
const newIndex = this.ungroupedColumns.findIndex(ungroupedColumn => ungroupedColumn?.field === column?.field);
const newItem = this.groupItems.get(newIndex + this.groupedColumns.length);
if (newItem) {
newItem.nativeElement.focus();
}
});
}
moveGroupUp(column, ev) {
ev.stopImmediatePropagation();
const index = this.group.findIndex(x => x.field === column?.field);
if (index > 0) {
const groupToMove = this.group[index];
this.group.splice(index, 1);
this.group.splice(index - 1, 0, groupToMove);
this.ctx.grid.groupChange.emit(this.group);
this.updateGroupedColumns();
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
const newItem = this.groupItems.get(index - 1);
if (newItem) {
newItem.nativeElement.focus();
this.currentFocusedItemIndex = index - 1;
}
});
}
}
moveGroupDown(column, ev) {
ev.stopImmediatePropagation();
const index = this.group.findIndex(x => x.field === column?.field);
if (index < this.group.length - 1) {
const groupToMove = this.group[index];
this.group.splice(index, 1);
this.group.splice(index + 1, 0, groupToMove);
this.ctx.grid.groupChange.emit(this.group);
this.updateGroupedColumns();
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
const newItem = this.groupItems.get(index + 1);
if (newItem) {
newItem.nativeElement.focus();
this.currentFocusedItemIndex = index + 1;
}
});
}
}
clear() {
this.group = [];
this.ctx.grid.groupChange.emit(this.group);
this.groupClear.emit(this.group);
}
getColumnComponent(column) {
return column;
}
onItemFocus(groupIndex, index) {
const currentIndex = (typeof groupIndex === 'number' ? groupIndex : this.groupedColumns?.length || 0) + index;
this.currentFocusedItemIndex = currentIndex;
}
handleGroupedKeydown(column, index, ev) {
const code = normalizeKeys(ev);
if (code === Keys.Enter || code === Keys.Backspace || code === Keys.Delete) {
this.removeGroup(column, ev);
}
else if (code === Keys.ArrowUp && ev.shiftKey) {
this.moveGroupUp(column, ev);
}
else if (code === Keys.ArrowDown && ev.shiftKey) {
this.moveGroupDown(column, ev);
}
else if (code === Keys.ArrowUp) {
this.navigateToPreviousItem();
}
else if (code === Keys.ArrowDown) {
this.navigateToNextItem();
}
}
handleUngroupedKeydown(column, index, ev) {
const code = normalizeKeys(ev);
if (code === Keys.Enter) {
this.addGroup(column, ev);
}
else if (code === Keys.ArrowUp) {
this.navigateToPreviousItem();
}
else if (code === Keys.ArrowDown) {
this.navigateToNextItem();
}
}
updateGroupedColumns() {
const columns = this.ctx.grid['columnInfoService'].leafNamedColumns;
const groupableColumns = columns.filter(column => column?.groupable);
this.groupedColumns = this.group
.map(group => columns.find(column => column?.field === group.field))
.filter(column => !!column);
this.ungroupedColumns = groupableColumns.filter(column => !this.groupedColumns.some(gc => gc?.field === column?.field));
}
navigateToNextItem() {
if (this.currentFocusedItemIndex < this.groupItems.length - 1) {
this.currentFocusedItemIndex++;
this.groupItems.get(this.currentFocusedItemIndex).nativeElement.focus();
}
else if (this.currentFocusedItemIndex === this.groupItems.length - 1) {
this.currentFocusedItemIndex = 0;
this.groupItems.get(this.currentFocusedItemIndex).nativeElement.focus();
}
}
navigateToPreviousItem() {
if (this.currentFocusedItemIndex > 0) {
this.currentFocusedItemIndex--;
this.groupItems.get(this.currentFocusedItemIndex).nativeElement.focus();
}
else if (this.currentFocusedItemIndex === 0) {
this.currentFocusedItemIndex = this.groupItems.length - 1;
this.groupItems.get(this.currentFocusedItemIndex).nativeElement.focus();
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GroupToolbarToolComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: GroupToolbarToolComponent, isStandalone: true, selector: "kendo-group-toolbar-tool", inputs: { adaptive: "adaptive" }, outputs: { close: "close", groupClear: "groupClear" }, host: { listeners: { "keydown.escape": "onEscKeyDown($event)" }, properties: { "class.k-group-menu": "this.hostClass", "class.k-group-menu-lg": "this.lgClass", "class.k-group-menu-md": "this.mdClass" } }, viewQueries: [{ propertyName: "groupItems", predicate: ["groupItem"], descendants: true, read: ElementRef }], ngImport: i0, template: `
(groupedColumns.length) {
<div class="k-group-menu-item-wrap">
(column of groupedColumns; track column; let i = $index) {
<div
#groupItem
role="button"
class="k-group-menu-item"
tabindex="0"
(keydown)="handleGroupedKeydown(column, i, $event)"
(focus)="onItemFocus(i, 0)"
>
(groupedColumns.length > 1) {
<span class="k-group-menu-item-actions">
<span
class="k-group-menu-item-action k-group-menu-item-up-action"
(click)="moveGroupUp(column, $event)"
[attr.aria-disabled]="i === 0"
[class.k-disabled]="i === 0"
>
<kendo-icon-wrapper
name="arrow-chevron-up"
[svgIcon]="upIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
<span
class="k-group-menu-item-action k-group-menu-item-down-action"
(click)="moveGroupDown(column, $event)"
[attr.aria-disabled]="i === groupedColumns.length - 1"
[class.k-disabled]="i === groupedColumns.length - 1"
>
<kendo-icon-wrapper
name="arrow-chevron-down"
[svgIcon]="downIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
</span>
}
<span class="k-group-item-text">{{column.title || getColumnComponent(column).field}}</span>
<span class="k-spacer"></span>
<span class="k-group-menu-item-actions">
<span class="k-group-menu-item-action k-group-menu-item-remove-action" (click)="removeGroup(column, $event)">
<kendo-icon-wrapper
name="x-circle"
[svgIcon]="removeIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
</span>
</div>
}
</div>
}
(ungroupedColumns.length) {
<div class="k-group-menu-item-wrap">
(column of ungroupedColumns; track column; let i = $index) {
<div
#groupItem
role="button"
class="k-group-menu-item"
tabindex="0"
(keydown)="handleUngroupedKeydown(column, i, $event)"
(focus)="onItemFocus(null, i)"
>
<span class="k-group-item-text">{{column.title || getColumnComponent(column).field}}</span>
<span class="k-spacer"></span>
<span class="k-group-menu-item-actions">
<span class="k-group-menu-item-action k-group-menu-item-add-action" (click)="addGroup(column, $event)">
<kendo-icon-wrapper
name="plus-circle"
[svgIcon]="addIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
</span>
</div>
}
</div>
}
(!adaptive) {
<div class="k-actions k-actions-stretched k-actions-horizontal k-column-menu-footer">
<button kendoButton
[svgIcon]="clearIcon"
(click)="clear()"
icon="x"
>
{{ctx?.localization.get('groupClearButton')}}
</button>
</div>
}
`, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i1.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GroupToolbarToolComponent, decorators: [{
type: Component,
args: [{
selector: 'kendo-group-toolbar-tool',
template: `
(groupedColumns.length) {
<div class="k-group-menu-item-wrap">
(column of groupedColumns; track column; let i = $index) {
<div
#groupItem
role="button"
class="k-group-menu-item"
tabindex="0"
(keydown)="handleGroupedKeydown(column, i, $event)"
(focus)="onItemFocus(i, 0)"
>
(groupedColumns.length > 1) {
<span class="k-group-menu-item-actions">
<span
class="k-group-menu-item-action k-group-menu-item-up-action"
(click)="moveGroupUp(column, $event)"
[attr.aria-disabled]="i === 0"
[class.k-disabled]="i === 0"
>
<kendo-icon-wrapper
name="arrow-chevron-up"
[svgIcon]="upIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
<span
class="k-group-menu-item-action k-group-menu-item-down-action"
(click)="moveGroupDown(column, $event)"
[attr.aria-disabled]="i === groupedColumns.length - 1"
[class.k-disabled]="i === groupedColumns.length - 1"
>
<kendo-icon-wrapper
name="arrow-chevron-down"
[svgIcon]="downIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
</span>
}
<span class="k-group-item-text">{{column.title || getColumnComponent(column).field}}</span>
<span class="k-spacer"></span>
<span class="k-group-menu-item-actions">
<span class="k-group-menu-item-action k-group-menu-item-remove-action" (click)="removeGroup(column, $event)">
<kendo-icon-wrapper
name="x-circle"
[svgIcon]="removeIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
</span>
</div>
}
</div>
}
(ungroupedColumns.length) {
<div class="k-group-menu-item-wrap">
(column of ungroupedColumns; track column; let i = $index) {
<div
#groupItem
role="button"
class="k-group-menu-item"
tabindex="0"
(keydown)="handleUngroupedKeydown(column, i, $event)"
(focus)="onItemFocus(null, i)"
>
<span class="k-group-item-text">{{column.title || getColumnComponent(column).field}}</span>
<span class="k-spacer"></span>
<span class="k-group-menu-item-actions">
<span class="k-group-menu-item-action k-group-menu-item-add-action" (click)="addGroup(column, $event)">
<kendo-icon-wrapper
name="plus-circle"
[svgIcon]="addIcon"
[size]="iconSize"
></kendo-icon-wrapper>
</span>
</span>
</div>
}
</div>
}
(!adaptive) {
<div class="k-actions k-actions-stretched k-actions-horizontal k-column-menu-footer">
<button kendoButton
[svgIcon]="clearIcon"
(click)="clear()"
icon="x"
>
{{ctx?.localization.get('groupClearButton')}}
</button>
</div>
}
`,
standalone: true,
imports: [IconWrapperComponent, KENDO_BUTTON]
}]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { hostClass: [{
type: HostBinding,
args: ['class.k-group-menu']
}], lgClass: [{
type: HostBinding,
args: ['class.k-group-menu-lg']
}], mdClass: [{
type: HostBinding,
args: ['class.k-group-menu-md']
}], onEscKeyDown: [{
type: HostListener,
args: ['keydown.escape', ['$event']]
}], groupItems: [{
type: ViewChildren,
args: ['groupItem', { read: ElementRef }]
}], adaptive: [{
type: Input
}], close: [{
type: Output
}], groupClear: [{
type: Output
}] } });