or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cell-editing.mdcolumn-system.mdcore-grid.mddata-management.mdevent-system.mdindex.mdnavigation-scrolling.mdplugin-system.mdselection-focus.mdtypes-interfaces.md

navigation-scrolling.mddocs/

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.