• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.55
/projects/igniteui-angular/src/lib/grids/grid-mrl-navigation.service.ts
1
import { Injectable } from '@angular/core';
2
import { first } from 'rxjs/operators';
3
import { IgxGridNavigationService } from './grid-navigation.service';
4
import { HORIZONTAL_NAV_KEYS, HEADER_KEYS } from '../core/utils';
5
import { GridKeydownTargetType } from './common/enums';
6
import { ColumnType } from './common/grid.interface';
7

8
/** @hidden */
9
@Injectable()
10
export class IgxGridMRLNavigationService extends IgxGridNavigationService {
2✔
11

12
    public override isValidPosition(rowIndex: number, colIndex: number): boolean {
UNCOV
13
        if (rowIndex < 0 || colIndex < 0 || this.grid.dataView.length - 1 < rowIndex ||
×
UNCOV
14
            Math.max(...this.grid.visibleColumns.map(col => col.visibleIndex)) < colIndex ||
×
15
            (this.activeNode.column !== colIndex && !this.isDataRow(rowIndex, true))) {
16
            return false;
×
17
        }
UNCOV
18
        return true;
×
19
    }
20

21
    public override shouldPerformVerticalScroll(targetRowIndex: number, visibleColIndex: number): boolean {
UNCOV
22
        if (!super.shouldPerformVerticalScroll(targetRowIndex, visibleColIndex)) {
×
UNCOV
23
            return false;
×
24
        }
UNCOV
25
        if (!this.isDataRow(targetRowIndex) || visibleColIndex < 0) {
×
UNCOV
26
            return super.shouldPerformVerticalScroll(targetRowIndex, visibleColIndex);
×
27
        }
28

UNCOV
29
        const targetRow = super.getRowElementByIndex(targetRowIndex);
×
UNCOV
30
        const containerHeight = this.grid.calcHeight ? Math.ceil(this.grid.calcHeight) : 0;
×
UNCOV
31
        const scrollPos = this.getVerticalScrollPositions(targetRowIndex, visibleColIndex);
×
UNCOV
32
        return (!targetRow || targetRow.offsetTop + scrollPos.topOffset < Math.abs(this.containerTopOffset)
×
33
            || containerHeight && containerHeight < scrollPos.rowBottom -  Math.ceil(this.scrollTop));
34
    }
35

36
    public override isColumnFullyVisible(visibleColIndex: number): boolean {
UNCOV
37
        const targetCol = this.grid.getColumnByVisibleIndex(visibleColIndex);
×
UNCOV
38
        if (this.isParentColumnFullyVisible(targetCol?.parent) || super.isColumnPinned(visibleColIndex, this.forOfDir())) {
×
UNCOV
39
            return true;
×
40
        }
41

UNCOV
42
        const scrollPos = this.getChildColumnScrollPositions(visibleColIndex);
×
UNCOV
43
        const colWidth = scrollPos.rightScroll - scrollPos.leftScroll;
×
UNCOV
44
        if (this.displayContainerWidth < colWidth && this.displayContainerScrollLeft === scrollPos.leftScroll) {
×
UNCOV
45
            return true;
×
46
        }
UNCOV
47
        return this.displayContainerWidth >= scrollPos.rightScroll - this.displayContainerScrollLeft &&
×
48
            this.displayContainerScrollLeft <= scrollPos.leftScroll;
49
    }
50

51
    public getVerticalScrollPositions(rowIndex: number, visibleIndex: number) {
UNCOV
52
        const targetCol = this.grid.getColumnByVisibleIndex(visibleIndex);
×
UNCOV
53
        const rowSpan = targetCol.rowEnd && targetCol.rowEnd - targetCol.rowStart ? targetCol.rowEnd - targetCol.rowStart : 1;
×
UNCOV
54
        const topOffset = this.grid.defaultRowHeight * (targetCol.rowStart - 1);
×
UNCOV
55
        const rowTop = this.grid.verticalScrollContainer.sizesCache[rowIndex] + topOffset;
×
UNCOV
56
        return { topOffset, rowTop, rowBottom: rowTop + (this.grid.defaultRowHeight * rowSpan) };
×
57
    }
58

59
    public override performHorizontalScrollToCell(visibleColumnIndex: number, cb?: () => void) {
UNCOV
60
        if (!this.shouldPerformHorizontalScroll(visibleColumnIndex)) {
×
UNCOV
61
            return;
×
62
        }
UNCOV
63
        const scrollPos = this.getChildColumnScrollPositions(visibleColumnIndex);
×
UNCOV
64
        const startScroll = scrollPos.rightScroll - this.displayContainerScrollLeft;
×
UNCOV
65
        const nextScroll = !(this.displayContainerScrollLeft <= scrollPos.leftScroll) && this.displayContainerWidth >= startScroll ?
×
66
            scrollPos.leftScroll : scrollPos.rightScroll - this.displayContainerWidth;
UNCOV
67
        this.forOfDir().getScroll().scrollLeft = nextScroll;
×
UNCOV
68
        this.grid.parentVirtDir.chunkLoad
×
69
            .pipe(first())
70
            .subscribe(() => {
UNCOV
71
                if (cb) {
×
UNCOV
72
                    cb();
×
73
                }
74
            });
75
    }
76

77
    public override performVerticalScrollToCell(rowIndex: number, visibleColIndex: number, cb?: () => void) {
UNCOV
78
        const children = this.parentByChildIndex(visibleColIndex || 0)?.children;
×
UNCOV
79
        if (!super.isDataRow(rowIndex) || (children && children.length < 2) || visibleColIndex < 0) {
×
UNCOV
80
            return super.performVerticalScrollToCell(rowIndex, visibleColIndex, cb);
×
81
        }
82

UNCOV
83
        const containerHeight = this.grid.calcHeight ? Math.ceil(this.grid.calcHeight) : 0;
×
UNCOV
84
        const pos = this.getVerticalScrollPositions(rowIndex, visibleColIndex);
×
UNCOV
85
        const row = super.getRowElementByIndex(rowIndex);
×
UNCOV
86
        if ((this.scrollTop > pos.rowTop) && (!row || row.offsetTop + pos.topOffset < Math.abs(this.containerTopOffset))) {
×
UNCOV
87
            if (pos.topOffset === 0) {
×
UNCOV
88
                this.grid.verticalScrollContainer.scrollTo(rowIndex);
×
89
            } else {
UNCOV
90
                this.grid.verticalScrollContainer.scrollPosition = pos.rowTop;
×
91
            }
92
        } else {
UNCOV
93
            this.grid.verticalScrollContainer.addScrollTop(Math.abs(pos.rowBottom - this.scrollTop - containerHeight));
×
94
        }
UNCOV
95
        this.grid.verticalScrollContainer.chunkLoad
×
96
            .pipe(first()).subscribe(() => {
UNCOV
97
                if (cb) {
×
UNCOV
98
                    cb();
×
99
                }
100
            });
101
    }
102

103
    public getNextHorizontalCellPosition(previous = false) {
×
UNCOV
104
        const parent = this.parentByChildIndex(this.activeNode.column);
×
UNCOV
105
        if (!this.hasNextHorizontalPosition(previous, parent)) {
×
UNCOV
106
            return { row: this.activeNode.row, column: this.activeNode.column };
×
107
        }
UNCOV
108
        const columns = previous ? parent.children.filter(c => c.rowStart <= this.activeNode.layout.rowStart)
×
UNCOV
109
            .sort((a, b) => b.visibleIndex - a.visibleIndex) : parent.children.filter(c => c.rowStart <= this.activeNode.layout.rowStart);
×
UNCOV
110
        let column = columns.find((col) => previous ?
×
111
                col.visibleIndex < this.activeNode.column && this.rowEnd(col) > this.activeNode.layout.rowStart :
×
112
                col.visibleIndex > this.activeNode.column && col.colStart > this.activeNode.layout.colStart);
×
UNCOV
113
        if (!column || (previous && this.activeNode.layout.colStart === 1)) {
×
UNCOV
114
            const index = previous ? parent.visibleIndex - 1 : parent.visibleIndex + 1;
×
UNCOV
115
            const children = this.grid.columns.find(cols => cols.columnLayout && cols.visibleIndex === index).children;
×
UNCOV
116
            column = previous ? children.toArray().reverse().find(child => child.rowStart <= this.activeNode.layout.rowStart) :
×
UNCOV
117
                children.find(child => this.rowEnd(child) > this.activeNode.layout.rowStart && child.colStart === 1);
×
118
        }
UNCOV
119
        return { row: this.activeNode.row, column: column.visibleIndex };
×
120
    }
121

122
    public getNextVerticalPosition(previous = false) {
×
UNCOV
123
        this.activeNode.column = this.activeNode.column || 0;
×
UNCOV
124
        if (!this.hasNextVerticalPosition(previous)) {
×
UNCOV
125
            return { row: this.activeNode.row, column: this.activeNode.column };
×
126
        }
UNCOV
127
        const currentRowStart = this.grid.getColumnByVisibleIndex(this.activeNode.column).rowStart;
×
UNCOV
128
        const nextBlock = !this.isDataRow(this.activeNode.row) ||
×
129
        (previous ? currentRowStart === 1 : currentRowStart === this.lastRowStartPerBlock());
×
UNCOV
130
        const nextRI = previous ? this.activeNode.row - 1 : this.activeNode.row + 1;
×
UNCOV
131
        if (nextBlock && !this.isDataRow(nextRI)) {
×
UNCOV
132
            return {row: nextRI,  column: this.activeNode.column};
×
133
        }
UNCOV
134
        const children = this.parentByChildIndex(this.activeNode.column).children;
×
UNCOV
135
        const col = previous ? this.getPreviousRowIndex(children, nextBlock) : this.getNextRowIndex(children, nextBlock);
×
UNCOV
136
        return { row: nextBlock ? nextRI : this.activeNode.row, column: col.visibleIndex };
×
137
    }
138

139
    public override headerNavigation(event: KeyboardEvent) {
UNCOV
140
        const key = event.key.toLowerCase();
×
UNCOV
141
        if (!HEADER_KEYS.has(key)) {
×
142
            return;
×
143
        }
UNCOV
144
        event.preventDefault();
×
UNCOV
145
        if (!this.activeNode.layout) {
×
146
            this.activeNode.layout = this.layout(this.activeNode.column || 0);
×
147
        }
UNCOV
148
        const alt = event.altKey;
×
UNCOV
149
        const ctrl = event.ctrlKey;
×
UNCOV
150
        this.performHeaderKeyCombination(this.grid.getColumnByVisibleIndex(this.activeNode.column), key, event.shiftKey, ctrl, alt, event);
×
UNCOV
151
        if (!ctrl && !alt && (key.includes('down') || key.includes('up'))) {
×
UNCOV
152
            const children = this.parentByChildIndex(this.activeNode.column).children;
×
UNCOV
153
            const col = key.includes('down') ? this.getNextRowIndex(children, false) : this.getPreviousRowIndex(children, false);
×
UNCOV
154
            if (!col) {
×
155
                return;
×
156
            }
UNCOV
157
            this.activeNode.column = col.visibleIndex;
×
UNCOV
158
            const layout = this.layout(this.activeNode.column);
×
UNCOV
159
            const nextLayout = {...this.activeNode.layout, rowStart: layout.rowStart, rowEnd: layout.rowEnd};
×
UNCOV
160
            this.setActiveNode({row: this.activeNode.row, layout: nextLayout});
×
UNCOV
161
            return;
×
162
        }
UNCOV
163
        this.horizontalNav(event, key, -1, 'headerCell');
×
164
    }
165

166
    /**
167
     * @hidden
168
     * @internal
169
     */
170
    public layout(visibleIndex) {
UNCOV
171
        const column = this.grid.getColumnByVisibleIndex(visibleIndex);
×
UNCOV
172
        return {colStart: column.colStart, rowStart: column.rowStart,
×
173
                colEnd: column.colEnd, rowEnd: column.rowEnd, columnVisibleIndex: column.visibleIndex };
174
    }
175

176
    protected override getNextPosition(rowIndex: number, colIndex: number, key: string, shift: boolean, ctrl: boolean, event: KeyboardEvent) {
UNCOV
177
        if (!this.activeNode.layout) {
×
UNCOV
178
            this.activeNode.layout = this.layout(this.activeNode.column || 0);
×
179
        }
UNCOV
180
        switch (key) {
×
181
            case 'tab':
182
            case ' ':
183
            case 'spacebar':
184
            case 'space':
185
            case 'escape':
186
            case 'esc':
187
            case 'enter':
188
            case 'f2':
UNCOV
189
                super.getNextPosition(rowIndex, colIndex, key, shift, ctrl, event);
×
UNCOV
190
                break;
×
191
            case 'end':
UNCOV
192
                rowIndex = ctrl ? this.findLastDataRowIndex() : this.activeNode.row;
×
UNCOV
193
                colIndex = ctrl ? this.lastColIndexPerMRLBlock(this.lastIndexPerRow) : this.lastIndexPerRow;
×
UNCOV
194
                break;
×
195
            case 'home':
UNCOV
196
                rowIndex = ctrl ? this.findFirstDataRowIndex() : this.activeNode.row;
×
UNCOV
197
                colIndex = ctrl ? 0 : this.firstIndexPerRow;
×
UNCOV
198
                break;
×
199
            case 'arrowleft':
200
            case 'left':
UNCOV
201
                colIndex = ctrl ? this.firstIndexPerRow : this.getNextHorizontalCellPosition(true).column;
×
UNCOV
202
                break;
×
203
            case 'arrowright':
204
            case 'right':
UNCOV
205
                colIndex = ctrl ? this.lastIndexPerRow : this.getNextHorizontalCellPosition().column;
×
UNCOV
206
                break;
×
207
            case 'arrowup':
208
            case 'up':
UNCOV
209
                const prevPos = this.getNextVerticalPosition(true);
×
UNCOV
210
                colIndex = ctrl ? this.activeNode.column : prevPos.column;
×
UNCOV
211
                rowIndex = ctrl ? this.findFirstDataRowIndex() : prevPos.row;
×
UNCOV
212
                break;
×
213
            case 'arrowdown':
214
            case 'down':
UNCOV
215
                const nextPos = this.getNextVerticalPosition();
×
UNCOV
216
                colIndex = ctrl ? this.activeNode.column : nextPos.column;
×
UNCOV
217
                rowIndex = ctrl ? this.findLastDataRowIndex() : nextPos.row;
×
UNCOV
218
                break;
×
219
            default:
220
                return;
×
221
        }
UNCOV
222
        const nextLayout = this.layout(colIndex);
×
UNCOV
223
        const newLayout = key.includes('up') || key.includes('down') ? {rowStart: nextLayout.rowStart} : {colStart: nextLayout.colStart};
×
UNCOV
224
        Object.assign(this.activeNode.layout, newLayout, {rowEnd: nextLayout.rowEnd});
×
225

UNCOV
226
        if (ctrl && (key === 'home' || key === 'end')) {
×
UNCOV
227
            this.activeNode.layout = nextLayout;
×
228
        }
UNCOV
229
        return { rowIndex, colIndex };
×
230
    }
231

232
    protected override horizontalNav(event: KeyboardEvent, key: string, rowIndex: number, tag: GridKeydownTargetType) {
UNCOV
233
        const ctrl = event.ctrlKey;
×
UNCOV
234
        if (!HORIZONTAL_NAV_KEYS.has(key) || event.altKey) {
×
235
            return;
×
236
        }
UNCOV
237
        this.activeNode.row = rowIndex;
×
238

UNCOV
239
        const newActiveNode = {
×
240
            column: this.activeNode.column,
241
            mchCache: {
242
                level: this.activeNode.level,
243
                visibleIndex: this.activeNode.column
244
            }
245
        };
246

UNCOV
247
        if ((key.includes('left') || key === 'home') && this.activeNode.column > 0) {
×
UNCOV
248
            newActiveNode.column = ctrl || key === 'home' ? this.firstIndexPerRow : this.getNextHorizontalCellPosition(true).column;
×
249
        }
UNCOV
250
        if ((key.includes('right') || key === 'end') && this.activeNode.column !== this.lastIndexPerRow) {
×
UNCOV
251
            newActiveNode.column = ctrl || key === 'end' ? this.lastIndexPerRow : this.getNextHorizontalCellPosition().column;
×
252
        }
253

UNCOV
254
        if (tag === 'headerCell') {
×
UNCOV
255
            const column = this.grid.getColumnByVisibleIndex(newActiveNode.column);
×
UNCOV
256
            newActiveNode.mchCache.level = column.level;
×
UNCOV
257
            newActiveNode.mchCache.visibleIndex = column.visibleIndex;
×
258
        }
259

UNCOV
260
        const layout = this.layout(newActiveNode.column);
×
UNCOV
261
        const newLayout = {...this.activeNode.layout, colStart: layout.colStart, rowEnd: layout.rowEnd};
×
UNCOV
262
        this.setActiveNode({row: this.activeNode.row, column: newActiveNode.column,
×
263
            layout: newLayout, mchCache: newActiveNode.mchCache});
UNCOV
264
        this.performHorizontalScrollToCell(newActiveNode.column);
×
265
    }
266

267
    private isParentColumnFullyVisible(parent: ColumnType): boolean {
UNCOV
268
        if (!this.forOfDir().getScroll().clientWidth || parent?.pinned) {
×
UNCOV
269
            return true;
×
270
        }
271

UNCOV
272
        const index = this.forOfDir().igxForOf.indexOf(parent);
×
UNCOV
273
        return this.displayContainerWidth >= this.forOfDir().getColumnScrollLeft(index + 1) - this.displayContainerScrollLeft &&
×
274
            this.displayContainerScrollLeft <= this.forOfDir().getColumnScrollLeft(index);
275
    }
276

277
    private getChildColumnScrollPositions(visibleColIndex: number) {
UNCOV
278
        const targetCol = this.grid.getColumnByVisibleIndex(visibleColIndex);
×
UNCOV
279
        const parentVIndex = this.forOfDir().igxForOf.indexOf(targetCol.parent);
×
UNCOV
280
        let leftScroll = this.forOfDir().getColumnScrollLeft(parentVIndex);
×
UNCOV
281
        let rightScroll = this.forOfDir().getColumnScrollLeft(parentVIndex + 1);
×
UNCOV
282
        targetCol.parent.children.forEach((c) => {
×
UNCOV
283
            if (c.rowStart >= targetCol.rowStart && c.visibleIndex < targetCol.visibleIndex) {
×
UNCOV
284
                leftScroll += parseInt(c.width, 10);
×
285
            }
UNCOV
286
            if (c.rowStart <= targetCol.rowStart && c.visibleIndex > targetCol.visibleIndex) {
×
UNCOV
287
                rightScroll -= parseInt(c.width, 10);
×
288
            }
289
        });
UNCOV
290
        return { leftScroll, rightScroll };
×
291
    }
292

293
    private getNextRowIndex(children, next) {
UNCOV
294
        const rowStart = next ? 1 : this.rowEnd(this.grid.getColumnByVisibleIndex(this.activeNode.column));
×
UNCOV
295
        const  col = children.filter(c => c.rowStart === rowStart);
×
UNCOV
296
        return col.find(co => co.colStart === this.activeNode.layout.colStart) ||
×
UNCOV
297
            col.sort((a, b) => b.visibleIndex - a.visibleIndex).find(co => co.colStart <= this.activeNode.layout.colStart);
×
298
}
299

300
    private getPreviousRowIndex(children, prev) {
UNCOV
301
        const end = prev ? Math.max(...children.map(c => this.rowEnd(c))) :
×
302
            this.grid.getColumnByVisibleIndex(this.activeNode.column).rowStart;
UNCOV
303
        const col = children.filter(c => this.rowEnd(c) ===  end);
×
UNCOV
304
        return col.find(co => co.colStart === this.activeNode.layout.colStart) ||
×
UNCOV
305
            col.sort((a, b) => b.visibleIndex - a.visibleIndex).find(co => co.colStart <= this.activeNode.layout.colStart);
×
306
    }
307

308
    private get lastIndexPerRow(): number {
UNCOV
309
        const children = this.grid.visibleColumns.find(c => c.visibleIndex === this.lastLayoutIndex && c.columnLayout)
×
310
            .children.toArray().reverse();
UNCOV
311
        const column = children.find(co => co.rowStart === this.activeNode.layout.rowStart) ||
×
UNCOV
312
        children.find(co => co.rowStart <= this.activeNode.layout.rowStart);
×
UNCOV
313
        return column.visibleIndex;
×
314
    }
315

316
    private get firstIndexPerRow(): number {
UNCOV
317
        const children = this.grid.visibleColumns.find(c => c.visibleIndex === 0 && c.columnLayout).children;
×
UNCOV
318
        const column = children.find(co => co.rowStart === this.activeNode.layout.rowStart) ||
×
319
        children.find(co => co.rowStart <= this.activeNode.layout.rowStart);
×
UNCOV
320
        return column.visibleIndex;
×
321
    }
322

323
    private get lastLayoutIndex(): number {
UNCOV
324
        return Math.max(...this.grid.visibleColumns.filter(c => c.columnLayout).map(col => col.visibleIndex));
×
325
    }
326

327
    private get scrollTop(): number {
UNCOV
328
       return Math.abs(this.grid.verticalScrollContainer.getScroll().scrollTop);
×
329
    }
330

331
    private lastColIndexPerMRLBlock(visibleIndex = this.activeNode.column): number {
×
UNCOV
332
        return this.parentByChildIndex(visibleIndex).children.last.visibleIndex;
×
333
    }
334

335
    private lastRowStartPerBlock(visibleIndex = this.activeNode.column) {
×
UNCOV
336
        return Math.max(...this.parentByChildIndex(visibleIndex).children.map(c => c.rowStart));
×
337
    }
338

339
    private rowEnd(column): number {
UNCOV
340
        return column.rowEnd && column.rowEnd - column.rowStart ? column.rowStart + column.rowEnd - column.rowStart : column.rowStart + 1;
×
341
    }
342

343
    private parentByChildIndex(visibleIndex) {
UNCOV
344
        return this.grid.getColumnByVisibleIndex(visibleIndex)?.parent;
×
345

346
    }
347

348
    private hasNextHorizontalPosition(previous = false, parent) {
×
UNCOV
349
        if (previous && parent.visibleIndex === 0 && this.activeNode.layout.colStart === 1 ||
×
350
            !previous && parent.visibleIndex === this.lastLayoutIndex && this.activeNode.column === this.lastIndexPerRow) {
UNCOV
351
            return false;
×
352
        }
UNCOV
353
        return true;
×
354
    }
355

356
    private hasNextVerticalPosition(prev = false) {
×
UNCOV
357
        if ((prev && this.activeNode.row === 0 && (!this.isDataRow(this.activeNode.row) || this.activeNode.layout.rowStart === 1)) ||
×
358
            (!prev && this.activeNode.row >= this.grid.dataView.length - 1 && this.activeNode.column === this.lastColIndexPerMRLBlock())) {
UNCOV
359
            return false;
×
360
        }
UNCOV
361
        return true;
×
362
    }
363
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc