0
# Navigation and Scrolling System
1
2
RevoGrid provides sophisticated navigation and viewport management with virtual scrolling, programmatic navigation, and comprehensive scrolling events for handling large datasets efficiently.
3
4
## Virtual Scrolling Architecture
5
6
### Viewport Configuration
7
8
Control how the virtual scrolling system renders content:
9
10
```typescript { .api }
11
interface ViewportConfiguration {
12
frameSize: number;
13
rowSize: number;
14
colSize: number;
15
}
16
```
17
18
**frameSize** { .api }
19
```typescript
20
frameSize: number = 1
21
```
22
Number of rows/columns rendered outside the visible area. Higher values provide smoother scrolling but use more memory.
23
24
**rowSize** { .api }
25
```typescript
26
rowSize: number = 0
27
```
28
Default row height in pixels. When `0`, uses theme default height.
29
30
**colSize** { .api }
31
```typescript
32
colSize: number = 100
33
```
34
Default column width in pixels for columns without explicit width.
35
36
### Viewport State Types
37
38
```typescript { .api }
39
namespace RevoGrid {
40
interface Range {
41
start: number;
42
end: number;
43
}
44
45
interface PositionItem {
46
itemIndex: number;
47
start: number;
48
end: number;
49
}
50
51
interface VirtualPositionItem extends PositionItem {
52
size: number;
53
}
54
55
interface ViewportState {
56
// Visible range
57
range: Range;
58
59
// All positioned items
60
items: VirtualPositionItem[];
61
62
// Total viewport size
63
virtualSize: number;
64
65
// Current scroll position
66
scrollTop: number;
67
scrollLeft: number;
68
}
69
}
70
```
71
72
## Navigation Methods
73
74
### Programmatic Scrolling
75
76
**scrollToRow** { .api }
77
```typescript
78
async scrollToRow(coordinate?: number): Promise<void>
79
```
80
Scroll to a specific row index to make it visible in the viewport.
81
82
- **coordinate**: Row index to scroll to (0-based)
83
84
```typescript
85
// Scroll to row 100
86
await grid.scrollToRow(100);
87
88
// Scroll to last row
89
const data = await grid.getSource();
90
await grid.scrollToRow(data.length - 1);
91
92
// Scroll to first row
93
await grid.scrollToRow(0);
94
```
95
96
**scrollToColumnIndex** { .api }
97
```typescript
98
async scrollToColumnIndex(coordinate?: number): Promise<void>
99
```
100
Scroll to a specific column index to make it visible in the viewport.
101
102
- **coordinate**: Column index to scroll to (0-based)
103
104
```typescript
105
// Scroll to column 5
106
await grid.scrollToColumnIndex(5);
107
108
// Scroll to last column
109
const columns = await grid.getColumns();
110
await grid.scrollToColumnIndex(columns.length - 1);
111
```
112
113
**scrollToColumnProp** { .api }
114
```typescript
115
async scrollToColumnProp(prop: RevoGrid.ColumnProp): Promise<void>
116
```
117
Scroll to a column by its property name.
118
119
- **prop**: Column property identifier
120
121
```typescript
122
// Scroll to specific column by property
123
await grid.scrollToColumnProp('email');
124
125
// Scroll to column based on dynamic property
126
const targetColumn = 'user_' + userId;
127
await grid.scrollToColumnProp(targetColumn);
128
```
129
130
**scrollToCoordinate** { .api }
131
```typescript
132
async scrollToCoordinate(cell: Partial<Selection.Cell>): Promise<void>
133
```
134
Scroll to a specific cell coordinate to ensure it's visible.
135
136
- **cell**: Cell position with x (column) and y (row) coordinates
137
138
```typescript
139
// Scroll to specific cell
140
await grid.scrollToCoordinate({ x: 5, y: 100 });
141
142
// Scroll to cell and focus
143
await grid.scrollToCoordinate({ x: 2, y: 50 });
144
await grid.setCellsFocus({ x: 2, y: 50 });
145
146
// Scroll to cell based on search result
147
const searchResult = await findCellWithValue('John Doe');
148
if (searchResult) {
149
await grid.scrollToCoordinate(searchResult);
150
}
151
```
152
153
## Scrolling Events
154
155
### Viewport Scroll Events
156
157
**viewportscroll** { .api }
158
```typescript
159
grid.addEventListener('viewportscroll', (event) => {
160
const scrollEvent: RevoGrid.ViewPortScrollEvent = event.detail;
161
console.log('Viewport scrolled:', scrollEvent);
162
});
163
```
164
165
**ViewPortScrollEvent** { .api }
166
```typescript
167
interface ViewPortScrollEvent {
168
// Current scroll position
169
scrollTop: number;
170
scrollLeft: number;
171
172
// Viewport dimensions
173
clientHeight: number;
174
clientWidth: number;
175
176
// Total scrollable dimensions
177
scrollHeight: number;
178
scrollWidth: number;
179
180
// Visible range information
181
range: {
182
rowStart: number;
183
rowEnd: number;
184
colStart: number;
185
colEnd: number;
186
};
187
}
188
```
189
190
### Scroll Event Handling
191
192
```typescript
193
class ScrollManager {
194
private grid: HTMLRevoGridElement;
195
private lastScrollPosition = { top: 0, left: 0 };
196
197
constructor(grid: HTMLRevoGridElement) {
198
this.grid = grid;
199
this.setupScrollHandling();
200
}
201
202
private setupScrollHandling() {
203
this.grid.addEventListener('viewportscroll', (event) => {
204
this.handleScroll(event.detail);
205
});
206
}
207
208
private handleScroll(scrollEvent: RevoGrid.ViewPortScrollEvent) {
209
const { scrollTop, scrollLeft, range } = scrollEvent;
210
211
// Detect scroll direction
212
const scrollDirection = {
213
vertical: scrollTop > this.lastScrollPosition.top ? 'down' : 'up',
214
horizontal: scrollLeft > this.lastScrollPosition.left ? 'right' : 'left'
215
};
216
217
// Update scroll position tracking
218
this.lastScrollPosition = { top: scrollTop, left: scrollLeft };
219
220
// Handle lazy loading
221
this.handleLazyLoading(range, scrollDirection);
222
223
// Update scroll indicators
224
this.updateScrollIndicators(scrollEvent);
225
226
// Sync with external components
227
this.syncScrollPosition(scrollTop, scrollLeft);
228
}
229
230
private handleLazyLoading(
231
range: { rowStart: number; rowEnd: number; colStart: number; colEnd: number },
232
direction: { vertical: string; horizontal: string }
233
) {
234
const { rowStart, rowEnd } = range;
235
const buffer = 10; // Load ahead buffer
236
237
// Load more data when approaching end
238
if (direction.vertical === 'down') {
239
const totalRows = this.grid.source.length;
240
if (rowEnd > totalRows - buffer) {
241
this.loadMoreRows();
242
}
243
}
244
}
245
246
private updateScrollIndicators(scrollEvent: RevoGrid.ViewPortScrollEvent) {
247
const { scrollTop, scrollLeft, scrollHeight, scrollWidth, clientHeight, clientWidth } = scrollEvent;
248
249
// Calculate scroll percentages
250
const verticalPercent = (scrollTop / (scrollHeight - clientHeight)) * 100;
251
const horizontalPercent = (scrollLeft / (scrollWidth - clientWidth)) * 100;
252
253
// Update UI indicators
254
this.updateScrollbar('vertical', verticalPercent);
255
this.updateScrollbar('horizontal', horizontalPercent);
256
}
257
258
private updateScrollbar(direction: 'vertical' | 'horizontal', percent: number) {
259
const scrollbar = document.querySelector(`.custom-scrollbar-${direction}`);
260
if (scrollbar) {
261
const thumb = scrollbar.querySelector('.thumb');
262
if (thumb) {
263
const property = direction === 'vertical' ? 'top' : 'left';
264
(thumb as HTMLElement).style[property] = `${Math.min(Math.max(percent, 0), 100)}%`;
265
}
266
}
267
}
268
269
private loadMoreRows() {
270
// Implement lazy loading logic
271
console.log('Loading more rows...');
272
}
273
274
private syncScrollPosition(scrollTop: number, scrollLeft: number) {
275
// Sync with other components that need scroll position
276
document.dispatchEvent(new CustomEvent('grid-scroll', {
277
detail: { scrollTop, scrollLeft }
278
}));
279
}
280
}
281
```
282
283
## Advanced Navigation Features
284
285
### Smooth Scrolling
286
287
Implement smooth scrolling animations for better UX:
288
289
```typescript
290
class SmoothScrollManager {
291
private grid: HTMLRevoGridElement;
292
293
constructor(grid: HTMLRevoGridElement) {
294
this.grid = grid;
295
}
296
297
async smoothScrollToRow(targetRow: number, duration: number = 300): Promise<void> {
298
const currentScroll = await this.getCurrentScrollPosition();
299
const targetScroll = await this.calculateRowScrollPosition(targetRow);
300
301
await this.animateScroll(
302
currentScroll.scrollTop,
303
targetScroll,
304
duration,
305
'vertical'
306
);
307
}
308
309
async smoothScrollToColumn(targetColumn: number, duration: number = 300): Promise<void> {
310
const currentScroll = await this.getCurrentScrollPosition();
311
const targetScroll = await this.calculateColumnScrollPosition(targetColumn);
312
313
await this.animateScroll(
314
currentScroll.scrollLeft,
315
targetScroll,
316
duration,
317
'horizontal'
318
);
319
}
320
321
private async getCurrentScrollPosition(): Promise<{ scrollTop: number; scrollLeft: number }> {
322
return new Promise(resolve => {
323
const handler = (event: CustomEvent) => {
324
const { scrollTop, scrollLeft } = event.detail;
325
this.grid.removeEventListener('viewportscroll', handler);
326
resolve({ scrollTop, scrollLeft });
327
};
328
329
this.grid.addEventListener('viewportscroll', handler);
330
// Trigger scroll event to get current position
331
this.grid.scrollBy(0, 0);
332
});
333
}
334
335
private async calculateRowScrollPosition(rowIndex: number): Promise<number> {
336
// Calculate scroll position needed to show the target row
337
const rowHeight = this.grid.rowSize || 30; // Default or configured row height
338
return rowIndex * rowHeight;
339
}
340
341
private async calculateColumnScrollPosition(columnIndex: number): Promise<number> {
342
const columns = await this.grid.getColumns();
343
let position = 0;
344
345
for (let i = 0; i < columnIndex && i < columns.length; i++) {
346
position += columns[i].size || this.grid.colSize;
347
}
348
349
return position;
350
}
351
352
private async animateScroll(
353
start: number,
354
target: number,
355
duration: number,
356
direction: 'vertical' | 'horizontal'
357
): Promise<void> {
358
return new Promise(resolve => {
359
const startTime = performance.now();
360
const distance = target - start;
361
362
const animate = (currentTime: number) => {
363
const elapsed = currentTime - startTime;
364
const progress = Math.min(elapsed / duration, 1);
365
366
// Easing function (ease-out)
367
const easeOut = 1 - Math.pow(1 - progress, 3);
368
const currentPosition = start + (distance * easeOut);
369
370
// Apply scroll
371
if (direction === 'vertical') {
372
this.grid.scrollTop = currentPosition;
373
} else {
374
this.grid.scrollLeft = currentPosition;
375
}
376
377
if (progress < 1) {
378
requestAnimationFrame(animate);
379
} else {
380
resolve();
381
}
382
};
383
384
requestAnimationFrame(animate);
385
});
386
}
387
}
388
```
389
390
### Keyboard Navigation
391
392
Enhanced keyboard navigation with custom shortcuts:
393
394
```typescript
395
class KeyboardNavigationManager {
396
private grid: HTMLRevoGridElement;
397
private navigationMode: 'cell' | 'row' | 'column' = 'cell';
398
399
constructor(grid: HTMLRevoGridElement) {
400
this.grid = grid;
401
this.setupKeyboardNavigation();
402
}
403
404
private setupKeyboardNavigation() {
405
this.grid.addEventListener('keydown', (event) => {
406
this.handleKeyboardNavigation(event as KeyboardEvent);
407
});
408
}
409
410
private async handleKeyboardNavigation(event: KeyboardEvent) {
411
const focused = await this.grid.getFocused();
412
if (!focused) return;
413
414
const currentCell = focused.cell;
415
let newCell: Selection.Cell | null = null;
416
let shouldScroll = false;
417
418
switch (event.key) {
419
// Basic navigation
420
case 'ArrowUp':
421
newCell = { x: currentCell.x, y: Math.max(0, currentCell.y - 1) };
422
shouldScroll = true;
423
break;
424
425
case 'ArrowDown':
426
const maxRow = (await this.grid.getSource()).length - 1;
427
newCell = { x: currentCell.x, y: Math.min(maxRow, currentCell.y + 1) };
428
shouldScroll = true;
429
break;
430
431
case 'ArrowLeft':
432
newCell = { x: Math.max(0, currentCell.x - 1), y: currentCell.y };
433
shouldScroll = true;
434
break;
435
436
case 'ArrowRight':
437
const maxCol = (await this.grid.getColumns()).length - 1;
438
newCell = { x: Math.min(maxCol, currentCell.x + 1), y: currentCell.y };
439
shouldScroll = true;
440
break;
441
442
// Page navigation
443
case 'PageUp':
444
newCell = await this.getPageUpCell(currentCell);
445
shouldScroll = true;
446
break;
447
448
case 'PageDown':
449
newCell = await this.getPageDownCell(currentCell);
450
shouldScroll = true;
451
break;
452
453
// Home/End navigation
454
case 'Home':
455
if (event.ctrlKey) {
456
// Ctrl+Home: Go to top-left
457
newCell = { x: 0, y: 0 };
458
} else {
459
// Home: Go to first column in row
460
newCell = { x: 0, y: currentCell.y };
461
}
462
shouldScroll = true;
463
break;
464
465
case 'End':
466
if (event.ctrlKey) {
467
// Ctrl+End: Go to bottom-right
468
const maxRow = (await this.grid.getSource()).length - 1;
469
const maxCol = (await this.grid.getColumns()).length - 1;
470
newCell = { x: maxCol, y: maxRow };
471
} else {
472
// End: Go to last column in row
473
const maxCol = (await this.grid.getColumns()).length - 1;
474
newCell = { x: maxCol, y: currentCell.y };
475
}
476
shouldScroll = true;
477
break;
478
479
// Custom navigation shortcuts
480
case 'g':
481
if (event.ctrlKey) {
482
// Ctrl+G: Go to line (show dialog)
483
this.showGoToDialog();
484
event.preventDefault();
485
}
486
break;
487
488
case 'f':
489
if (event.ctrlKey) {
490
// Ctrl+F: Find in grid
491
this.showFindDialog();
492
event.preventDefault();
493
}
494
break;
495
}
496
497
if (newCell && shouldScroll) {
498
await this.navigateToCell(newCell);
499
event.preventDefault();
500
}
501
}
502
503
private async getPageUpCell(currentCell: Selection.Cell): Promise<Selection.Cell> {
504
const visibleRows = await this.getVisibleRowCount();
505
const newY = Math.max(0, currentCell.y - visibleRows);
506
return { x: currentCell.x, y: newY };
507
}
508
509
private async getPageDownCell(currentCell: Selection.Cell): Promise<Selection.Cell> {
510
const visibleRows = await this.getVisibleRowCount();
511
const maxRow = (await this.grid.getSource()).length - 1;
512
const newY = Math.min(maxRow, currentCell.y + visibleRows);
513
return { x: currentCell.x, y: newY };
514
}
515
516
private async getVisibleRowCount(): Promise<number> {
517
// Calculate based on grid height and row height
518
const gridHeight = this.grid.clientHeight;
519
const rowHeight = this.grid.rowSize || 30;
520
return Math.floor(gridHeight / rowHeight);
521
}
522
523
private async navigateToCell(cell: Selection.Cell) {
524
// Scroll to make cell visible
525
await this.grid.scrollToCoordinate(cell);
526
527
// Set focus
528
await this.grid.setCellsFocus(cell);
529
}
530
531
private showGoToDialog() {
532
const rowNumber = prompt('Go to row number:');
533
if (rowNumber) {
534
const row = parseInt(rowNumber) - 1; // Convert to 0-based
535
if (!isNaN(row) && row >= 0) {
536
this.navigateToCell({ x: 0, y: row });
537
}
538
}
539
}
540
541
private showFindDialog() {
542
const searchTerm = prompt('Find:');
543
if (searchTerm) {
544
this.findAndNavigate(searchTerm);
545
}
546
}
547
548
private async findAndNavigate(searchTerm: string) {
549
const data = await this.grid.getSource();
550
const columns = await this.grid.getColumns();
551
552
// Search through all cells
553
for (let y = 0; y < data.length; y++) {
554
for (let x = 0; x < columns.length; x++) {
555
const cellValue = String(data[y][columns[x].prop] || '');
556
if (cellValue.toLowerCase().includes(searchTerm.toLowerCase())) {
557
await this.navigateToCell({ x, y });
558
return true;
559
}
560
}
561
}
562
563
alert('Search term not found');
564
return false;
565
}
566
}
567
```
568
569
### Minimap Navigation
570
571
Implement a minimap for quick navigation in large datasets:
572
573
```typescript
574
class MinimapManager {
575
private grid: HTMLRevoGridElement;
576
private minimapCanvas: HTMLCanvasElement;
577
private minimapContainer: HTMLElement;
578
579
constructor(grid: HTMLRevoGridElement, minimapContainer: HTMLElement) {
580
this.grid = grid;
581
this.minimapContainer = minimapContainer;
582
this.createMinimap();
583
this.setupMinimapInteraction();
584
}
585
586
private createMinimap() {
587
this.minimapCanvas = document.createElement('canvas');
588
this.minimapCanvas.className = 'grid-minimap';
589
this.minimapCanvas.width = 200;
590
this.minimapCanvas.height = 150;
591
this.minimapContainer.appendChild(this.minimapCanvas);
592
593
// Initial render
594
this.updateMinimap();
595
596
// Update on scroll
597
this.grid.addEventListener('viewportscroll', () => {
598
this.updateMinimapViewport();
599
});
600
}
601
602
private async updateMinimap() {
603
const ctx = this.minimapCanvas.getContext('2d');
604
if (!ctx) return;
605
606
const data = await this.grid.getSource();
607
const columns = await this.grid.getColumns();
608
609
// Clear canvas
610
ctx.clearRect(0, 0, this.minimapCanvas.width, this.minimapCanvas.height);
611
612
// Calculate scaling
613
const scaleX = this.minimapCanvas.width / columns.length;
614
const scaleY = this.minimapCanvas.height / data.length;
615
616
// Draw data representation
617
data.forEach((row, y) => {
618
columns.forEach((column, x) => {
619
const value = row[column.prop];
620
const color = this.getColorForValue(value, column);
621
622
ctx.fillStyle = color;
623
ctx.fillRect(
624
x * scaleX,
625
y * scaleY,
626
Math.max(1, scaleX),
627
Math.max(1, scaleY)
628
);
629
});
630
});
631
632
// Draw viewport indicator
633
this.updateMinimapViewport();
634
}
635
636
private updateMinimapViewport() {
637
// Get current viewport information
638
this.grid.addEventListener('viewportscroll', (event) => {
639
const { range } = event.detail;
640
this.drawViewportIndicator(range);
641
}, { once: true });
642
643
// Trigger scroll event to get current viewport
644
this.grid.scrollBy(0, 0);
645
}
646
647
private drawViewportIndicator(range: any) {
648
const ctx = this.minimapCanvas.getContext('2d');
649
if (!ctx) return;
650
651
// Clear previous indicator
652
// (In a real implementation, you'd store the canvas state or redraw)
653
654
// Calculate indicator position
655
const scaleX = this.minimapCanvas.width / (await this.grid.getColumns()).length;
656
const scaleY = this.minimapCanvas.height / (await this.grid.getSource()).length;
657
658
const x = range.colStart * scaleX;
659
const y = range.rowStart * scaleY;
660
const width = (range.colEnd - range.colStart) * scaleX;
661
const height = (range.rowEnd - range.rowStart) * scaleY;
662
663
// Draw viewport rectangle
664
ctx.strokeStyle = '#ff0000';
665
ctx.lineWidth = 2;
666
ctx.strokeRect(x, y, width, height);
667
}
668
669
private getColorForValue(value: any, column: RevoGrid.ColumnRegular): string {
670
// Simple color mapping based on value type
671
if (value === null || value === undefined || value === '') {
672
return '#f0f0f0'; // Empty
673
}
674
675
if (typeof value === 'number') {
676
// Color based on magnitude
677
const normalized = Math.min(Math.abs(value) / 100, 1);
678
const intensity = Math.floor(255 * (1 - normalized));
679
return `rgb(${intensity}, ${intensity}, 255)`;
680
}
681
682
if (typeof value === 'string') {
683
// Color based on string length
684
const normalized = Math.min(value.length / 50, 1);
685
const intensity = Math.floor(255 * (1 - normalized));
686
return `rgb(255, ${intensity}, ${intensity})`;
687
}
688
689
return '#cccccc'; // Default
690
}
691
692
private setupMinimapInteraction() {
693
this.minimapCanvas.addEventListener('click', async (event) => {
694
const rect = this.minimapCanvas.getBoundingClientRect();
695
const x = event.clientX - rect.left;
696
const y = event.clientY - rect.top;
697
698
// Convert minimap coordinates to grid coordinates
699
const columns = await this.grid.getColumns();
700
const data = await this.grid.getSource();
701
702
const columnIndex = Math.floor((x / this.minimapCanvas.width) * columns.length);
703
const rowIndex = Math.floor((y / this.minimapCanvas.height) * data.length);
704
705
// Navigate to clicked position
706
await this.grid.scrollToCoordinate({
707
x: Math.max(0, Math.min(columnIndex, columns.length - 1)),
708
y: Math.max(0, Math.min(rowIndex, data.length - 1))
709
});
710
});
711
}
712
}
713
```
714
715
## Usage Examples
716
717
### Complete Navigation System
718
719
```typescript
720
class GridNavigationSystem {
721
private grid: HTMLRevoGridElement;
722
private scrollManager: ScrollManager;
723
private smoothScrollManager: SmoothScrollManager;
724
private keyboardManager: KeyboardNavigationManager;
725
private minimapManager?: MinimapManager;
726
727
constructor(grid: HTMLRevoGridElement, options: NavigationOptions = {}) {
728
this.grid = grid;
729
this.initialize(options);
730
}
731
732
private initialize(options: NavigationOptions) {
733
// Setup core managers
734
this.scrollManager = new ScrollManager(this.grid);
735
this.smoothScrollManager = new SmoothScrollManager(this.grid);
736
this.keyboardManager = new KeyboardNavigationManager(this.grid);
737
738
// Setup minimap if container provided
739
if (options.minimapContainer) {
740
this.minimapManager = new MinimapManager(this.grid, options.minimapContainer);
741
}
742
743
// Setup additional navigation features
744
this.setupNavigationControls();
745
this.setupScrollSynchronization();
746
}
747
748
private setupNavigationControls() {
749
// Add navigation buttons to UI
750
const navContainer = document.createElement('div');
751
navContainer.className = 'grid-navigation-controls';
752
753
// First/Last buttons
754
this.createNavButton(navContainer, 'First', () => this.goToFirst());
755
this.createNavButton(navContainer, 'Last', () => this.goToLast());
756
this.createNavButton(navContainer, 'Top', () => this.goToTop());
757
this.createNavButton(navContainer, 'Bottom', () => this.goToBottom());
758
759
// Insert navigation controls
760
this.grid.parentElement?.appendChild(navContainer);
761
}
762
763
private createNavButton(container: HTMLElement, label: string, handler: () => void) {
764
const button = document.createElement('button');
765
button.textContent = label;
766
button.addEventListener('click', handler);
767
container.appendChild(button);
768
}
769
770
private async goToFirst() {
771
await this.smoothScrollManager.smoothScrollToColumn(0);
772
await this.grid.setCellsFocus({ x: 0, y: 0 });
773
}
774
775
private async goToLast() {
776
const columns = await this.grid.getColumns();
777
await this.smoothScrollManager.smoothScrollToColumn(columns.length - 1);
778
await this.grid.setCellsFocus({ x: columns.length - 1, y: 0 });
779
}
780
781
private async goToTop() {
782
await this.smoothScrollManager.smoothScrollToRow(0);
783
const focused = await this.grid.getFocused();
784
const x = focused?.cell.x || 0;
785
await this.grid.setCellsFocus({ x, y: 0 });
786
}
787
788
private async goToBottom() {
789
const data = await this.grid.getSource();
790
await this.smoothScrollManager.smoothScrollToRow(data.length - 1);
791
const focused = await this.grid.getFocused();
792
const x = focused?.cell.x || 0;
793
await this.grid.setCellsFocus({ x, y: data.length - 1 });
794
}
795
796
private setupScrollSynchronization() {
797
// Sync scroll position with external elements
798
this.grid.addEventListener('viewportscroll', (event) => {
799
const { scrollTop, scrollLeft } = event.detail;
800
801
// Sync with external scroll indicators
802
document.dispatchEvent(new CustomEvent('grid-scroll-sync', {
803
detail: { scrollTop, scrollLeft }
804
}));
805
});
806
}
807
808
// Public API methods
809
async navigateToCell(x: number, y: number, smooth: boolean = true) {
810
if (smooth) {
811
await this.smoothScrollManager.smoothScrollToRow(y);
812
await this.smoothScrollManager.smoothScrollToColumn(x);
813
} else {
814
await this.grid.scrollToCoordinate({ x, y });
815
}
816
817
await this.grid.setCellsFocus({ x, y });
818
}
819
820
async findAndNavigate(searchValue: any, options: FindOptions = {}): Promise<boolean> {
821
const data = await this.grid.getSource();
822
const columns = await this.grid.getColumns();
823
824
const startRow = options.startRow || 0;
825
const startCol = options.startColumn || 0;
826
827
// Search from current position
828
for (let y = startRow; y < data.length; y++) {
829
const colStart = y === startRow ? startCol : 0;
830
831
for (let x = colStart; x < columns.length; x++) {
832
const cellValue = data[y][columns[x].prop];
833
834
if (this.matchesSearch(cellValue, searchValue, options)) {
835
await this.navigateToCell(x, y, options.smooth);
836
return true;
837
}
838
}
839
}
840
841
return false;
842
}
843
844
private matchesSearch(cellValue: any, searchValue: any, options: FindOptions): boolean {
845
const cellStr = String(cellValue || '');
846
const searchStr = String(searchValue);
847
848
if (options.caseSensitive) {
849
return cellStr.includes(searchStr);
850
} else {
851
return cellStr.toLowerCase().includes(searchStr.toLowerCase());
852
}
853
}
854
}
855
856
interface NavigationOptions {
857
minimapContainer?: HTMLElement;
858
smoothScrolling?: boolean;
859
customKeyBindings?: Record<string, () => void>;
860
}
861
862
interface FindOptions {
863
startRow?: number;
864
startColumn?: number;
865
caseSensitive?: boolean;
866
smooth?: boolean;
867
}
868
869
// Usage
870
const navigationSystem = new GridNavigationSystem(grid, {
871
minimapContainer: document.getElementById('minimap-container'),
872
smoothScrolling: true
873
});
874
875
// Navigate to specific cell with smooth animation
876
await navigationSystem.navigateToCell(10, 100, true);
877
878
// Search and navigate
879
const found = await navigationSystem.findAndNavigate('John', {
880
caseSensitive: false,
881
smooth: true
882
});
883
```
884
885
The navigation and scrolling system provides comprehensive tools for efficient navigation in large datasets with smooth animations, keyboard shortcuts, and visual aids like minimaps for enhanced user experience.