From 2173fce6724e68826373b1ad05bc52ed16679894 Mon Sep 17 00:00:00 2001 From: Karl Seamon Date: Mon, 21 Apr 2025 12:46:23 -0400 Subject: [PATCH] feat(cdk-experimental/popover-edit): Support skipping over annotated rows when the user moves focus up/down. --- .../popover-edit/constants.ts | 3 + .../popover-edit/focus-dispatcher.ts | 13 ++- .../popover-edit/popover-edit.spec.ts | 97 +++++++++++++++++++ 3 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/cdk-experimental/popover-edit/constants.ts b/src/cdk-experimental/popover-edit/constants.ts index 112f9ef9fc91..559e6cce66b7 100644 --- a/src/cdk-experimental/popover-edit/constants.ts +++ b/src/cdk-experimental/popover-edit/constants.ts @@ -23,3 +23,6 @@ export const EDIT_PANE_CLASS = 'cdk-edit-pane'; /** Selector for finding the edit lens pane. */ export const EDIT_PANE_SELECTOR = `.${EDIT_PANE_CLASS}, .mat-edit-pane`; + +/** Selector for table rows that should be skipped when moving focus. */ +export const SKIP_ROW_FOCUS_SELECTOR = '.cdk-popover-edit-skip-focus, .mat-popover-edit-skip-focus'; diff --git a/src/cdk-experimental/popover-edit/focus-dispatcher.ts b/src/cdk-experimental/popover-edit/focus-dispatcher.ts index 0bd56469364d..da296af54343 100644 --- a/src/cdk-experimental/popover-edit/focus-dispatcher.ts +++ b/src/cdk-experimental/popover-edit/focus-dispatcher.ts @@ -11,7 +11,12 @@ import {LEFT_ARROW, UP_ARROW, RIGHT_ARROW, DOWN_ARROW} from '@angular/cdk/keycod import {Injectable, inject} from '@angular/core'; import {PartialObserver} from 'rxjs'; -import {EDITABLE_CELL_SELECTOR, ROW_SELECTOR, TABLE_SELECTOR} from './constants'; +import { + EDITABLE_CELL_SELECTOR, + ROW_SELECTOR, + SKIP_ROW_FOCUS_SELECTOR, + TABLE_SELECTOR, +} from './constants'; import {closest} from './polyfill'; /** @@ -53,7 +58,11 @@ export class FocusDispatcher { const currentIndexWithinRow = Array.from( currentRow.querySelectorAll(EDITABLE_CELL_SELECTOR), ).indexOf(currentCell); - const newRowIndex = currentRowIndex + offset; + + let newRowIndex = currentRowIndex + offset; + while (rows[newRowIndex]?.matches(SKIP_ROW_FOCUS_SELECTOR)) { + newRowIndex = newRowIndex + (offset > 0 ? 1 : -1); + } if (rows[newRowIndex]) { const rowToFocus = Array.from( diff --git a/src/cdk-experimental/popover-edit/popover-edit.spec.ts b/src/cdk-experimental/popover-edit/popover-edit.spec.ts index 6ad6476cdde2..c08a22b7c6fa 100644 --- a/src/cdk-experimental/popover-edit/popover-edit.spec.ts +++ b/src/cdk-experimental/popover-edit/popover-edit.spec.ts @@ -1044,6 +1044,103 @@ cdkPopoverEditTabOut`, fakeAsync(() => { } }); +@Component({ + template: ` +
+ + + + + + + + + + + + + + + + + + + +
+ just a cell + + ${CELL_TEMPLATE} + + + ${NAME_EDIT_TEMPLATE} + + + + + + + {{element.weight}} + + + ${WEIGHT_EDIT_TEMPLATE} + +
+
+ `, + standalone: false, +}) +class CdkTableWithSkipRows extends BaseTestComponent { + displayedColumns = ['before', 'name', 'weight']; + dataSource: ElementDataSource; + + renderData() { + this.dataSource = new ElementDataSource(); + this.cdr.markForCheck(); + } +} + +describe('CDK Popover Edit - with focus ignore rows', () => { + let component: CdkTableWithSkipRows; + let fixture: ComponentFixture; + + const dispatchKey = (cell: HTMLElement, keyCode: number) => + dispatchKeyboardEvent(cell, 'keydown', keyCode); + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + imports: [CdkTableModule, CdkPopoverEditModule, FormsModule, BidiModule], + declarations: [CdkTableWithSkipRows], + }); + fixture = TestBed.createComponent(CdkTableWithSkipRows); + component = fixture.componentInstance; + component.renderData(); + fixture.detectChanges(); + tick(10); + fixture.detectChanges(); + })); + + it('skips ignored rows when moving focus up', () => { + const rows = component.getRows(); + + getCells(rows[4])[1].focus(); + + dispatchKey(getCells(rows[4])[1], UP_ARROW); + expect(document.activeElement).toBe(getCells(rows[2])[1]); + }); + + it('skips ignored rows when moving focus down', () => { + const rows = component.getRows(); + + getCells(rows[0])[1].focus(); + + dispatchKey(getCells(rows[0])[1], DOWN_ARROW); + expect(document.activeElement).toBe(getCells(rows[2])[1]); + }); +}); + interface ChemicalElement { name: string; weight: number;