Skip to content

Commit 2a13f69

Browse files
authored
refactor(aria/grid): remove range selection to reduce computational overhead (#33055)
1 parent c25e625 commit 2a13f69

File tree

8 files changed

+7
-284
lines changed

8 files changed

+7
-284
lines changed

goldens/aria/grid/index.api.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export class Grid {
1515
readonly colWrap: _angular_core.InputSignal<"continuous" | "loop" | "nowrap">;
1616
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1717
readonly element: HTMLElement;
18-
readonly enableRangeSelection: _angular_core.InputSignalWithTransform<boolean, unknown>;
1918
readonly enableSelection: _angular_core.InputSignalWithTransform<boolean, unknown>;
2019
readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">;
2120
readonly multi: _angular_core.InputSignalWithTransform<boolean, unknown>;
@@ -25,7 +24,7 @@ export class Grid {
2524
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
2625
readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>;
2726
// (undocumented)
28-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Grid, "[ngGrid]", ["ngGrid"], { "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "rowWrap": { "alias": "rowWrap"; "required": false; "isSignal": true; }; "colWrap": { "alias": "colWrap"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "enableRangeSelection": { "alias": "enableRangeSelection"; "required": false; "isSignal": true; }; }, {}, ["_rows"], never, true, never>;
27+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Grid, "[ngGrid]", ["ngGrid"], { "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "rowWrap": { "alias": "rowWrap"; "required": false; "isSignal": true; }; "colWrap": { "alias": "colWrap"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; }, {}, ["_rows"], never, true, never>;
2928
// (undocumented)
3029
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Grid, never>;
3130
}
@@ -58,10 +57,10 @@ export class GridCell {
5857
export class GridCellWidget {
5958
constructor();
6059
activate(): void;
61-
readonly activated: _angular_core.OutputEmitterRef<FocusEvent | KeyboardEvent | undefined>;
60+
readonly activated: _angular_core.OutputEmitterRef<KeyboardEvent | FocusEvent | undefined>;
6261
readonly active: Signal<boolean>;
6362
deactivate(): void;
64-
readonly deactivated: _angular_core.OutputEmitterRef<FocusEvent | KeyboardEvent | undefined>;
63+
readonly deactivated: _angular_core.OutputEmitterRef<KeyboardEvent | FocusEvent | undefined>;
6564
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
6665
readonly element: HTMLElement;
6766
readonly focusTarget: _angular_core.InputSignal<ElementRef<any> | HTMLElement | undefined>;

goldens/aria/private/index.api.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,6 @@ export class GridCellWidgetPattern {
363363
// @public
364364
export interface GridInputs extends Omit<GridInputs$1<GridCellPattern>, 'cells'> {
365365
element: SignalLike<HTMLElement>;
366-
enableRangeSelection: SignalLike<boolean>;
367366
enableSelection: SignalLike<boolean>;
368367
getCell: (e: Element | null) => GridCellPattern | undefined;
369368
multi: SignalLike<boolean>;
@@ -375,7 +374,6 @@ export interface GridInputs extends Omit<GridInputs$1<GridCellPattern>, 'cells'>
375374
// @public
376375
export class GridPattern {
377376
constructor(inputs: GridInputs);
378-
readonly acceptsPointerMove: SignalLike<boolean>;
379377
readonly activeCell: SignalLike<GridCellPattern | undefined>;
380378
readonly activeDescendant: SignalLike<string | undefined>;
381379
readonly anchorCell: SignalLike<GridCellPattern | undefined>;
@@ -395,11 +393,8 @@ export class GridPattern {
395393
onFocusOut(event: FocusEvent): void;
396394
onKeydown(event: KeyboardEvent): void;
397395
onPointerdown(event: PointerEvent): void;
398-
onPointermove(event: PointerEvent): void;
399-
onPointerup(event: PointerEvent): void;
400396
readonly pauseNavigation: SignalLike<boolean>;
401397
readonly pointerdown: SignalLike<PointerEventManager<PointerEvent>>;
402-
readonly pointerup: SignalLike<PointerEventManager<PointerEvent>>;
403398
readonly prevColKey: SignalLike<"ArrowRight" | "ArrowLeft">;
404399
resetFocusEffect(): void;
405400
resetStateEffect(): void;

src/aria/grid/grid.spec.ts

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,6 @@ describe('Grid directives', () => {
6363
fixture.detectChanges();
6464
};
6565

66-
const pointerMove = (target: HTMLElement | Window, eventInit: PointerEventInit = {}) => {
67-
target.dispatchEvent(new PointerEvent('pointermove', {bubbles: true, ...eventInit}));
68-
fixture.detectChanges();
69-
};
70-
71-
const pointerUp = (target: HTMLElement | Window, eventInit: PointerEventInit = {}) => {
72-
target.dispatchEvent(new PointerEvent('pointerup', {bubbles: true, ...eventInit}));
73-
fixture.detectChanges();
74-
};
75-
7666
const up = (modifierKeys?: ModifierKeys) => keydown('ArrowUp', modifierKeys);
7767
const down = (modifierKeys?: ModifierKeys) => keydown('ArrowDown', modifierKeys);
7868
const left = (modifierKeys?: ModifierKeys) => keydown('ArrowLeft', modifierKeys);
@@ -105,7 +95,6 @@ describe('Grid directives', () => {
10595
softDisabled?: boolean;
10696
enableSelection?: boolean;
10797
selectionMode?: 'follow' | 'explicit';
108-
enableRangeSelection?: boolean;
10998
gridData?: RowConfig[];
11099
}) {
111100
TestBed.resetTestingModule();
@@ -122,8 +111,6 @@ describe('Grid directives', () => {
122111
if (opts?.enableSelection !== undefined)
123112
testComponent.enableSelection.set(opts.enableSelection);
124113
if (opts?.selectionMode !== undefined) testComponent.selectionMode.set(opts.selectionMode);
125-
if (opts?.enableRangeSelection !== undefined)
126-
testComponent.enableRangeSelection.set(opts.enableRangeSelection);
127114

128115
if (opts?.gridData !== undefined) {
129116
testComponent.gridData.set(opts.gridData);
@@ -388,7 +375,6 @@ describe('Grid directives', () => {
388375
enableSelection: true,
389376
selectionMode: 'explicit',
390377
multi: true,
391-
enableRangeSelection: true,
392378
});
393379
gridInstance._pattern.setDefaultStateEffect();
394380
fixture.detectChanges();
@@ -488,7 +474,6 @@ describe('Grid directives', () => {
488474
enableSelection: true,
489475
selectionMode: 'explicit',
490476
multi: true,
491-
enableRangeSelection: true,
492477
});
493478
gridInstance._pattern.setDefaultStateEffect();
494479
fixture.detectChanges();
@@ -503,41 +488,6 @@ describe('Grid directives', () => {
503488
expect(cell.getAttribute('aria-selected')).toBe('true');
504489
expect(getActiveCellId()).toBe('c1-1');
505490
});
506-
507-
it('should expand selection on pointermove while dragging without changing active cell', () => {
508-
const startCell = gridElement.querySelector('#c0-0') as HTMLElement;
509-
const dragCell = gridElement.querySelector('#c1-1') as HTMLElement;
510-
511-
pointerDown(startCell);
512-
pointerMove(dragCell);
513-
514-
expect(getActiveCellId()).toBe('c0-0');
515-
// Dragging expands selection
516-
expect(startCell.getAttribute('aria-selected')).toBe('true');
517-
expect(dragCell.getAttribute('aria-selected')).toBe('true');
518-
});
519-
520-
it('should stop dragging on pointerup', () => {
521-
const startCell = gridElement.querySelector('#c0-0') as HTMLElement;
522-
const endCell = gridElement.querySelector('#c1-1') as HTMLElement;
523-
524-
pointerDown(startCell);
525-
pointerUp(gridElement);
526-
pointerMove(endCell);
527-
528-
// Active cell should still be c0-0 because dragging stopped before moving to c1-1
529-
expect(getActiveCellId()).toBe('c0-0');
530-
expect(endCell.getAttribute('aria-selected')).toBe('false');
531-
});
532-
533-
it('should not change active cell on pointermove outside of the grid cells', () => {
534-
const startCell = gridElement.querySelector('#c0-0') as HTMLElement;
535-
536-
pointerDown(startCell);
537-
pointerMove(gridElement);
538-
539-
expect(getActiveCellId()).toBe('c0-0');
540-
});
541491
});
542492

543493
describe('configuration', () => {
@@ -1009,8 +959,7 @@ describe('Grid directives', () => {
1009959
[focusMode]="focusMode()"
1010960
[softDisabled]="softDisabled()"
1011961
[enableSelection]="enableSelection()"
1012-
[selectionMode]="selectionMode()"
1013-
[enableRangeSelection]="enableRangeSelection()">
962+
[selectionMode]="selectionMode()">
1014963
@for (row of gridData(); track $index; let rIndex = $index) {
1015964
<tr ngGridRow [rowIndex]="row.rowIndex">
1016965
@for (cell of row.cells; track $index; let cIndex = $index) {
@@ -1067,7 +1016,6 @@ class GridTestComponent {
10671016
readonly softDisabled = signal(true);
10681017
readonly enableSelection = signal(false);
10691018
readonly selectionMode = signal<'follow' | 'explicit'>('follow');
1070-
readonly enableRangeSelection = signal(false);
10711019
readonly gridData = signal<RowConfig[]>(createGridData());
10721020

10731021
onActivated = jasmine.createSpy('activated');

src/aria/grid/grid.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
ElementRef,
1616
inject,
1717
input,
18-
NgZone,
1918
Signal,
2019
} from '@angular/core';
2120
import {Directionality} from '@angular/cdk/bidi';
@@ -56,7 +55,6 @@ import {GRID_ROW} from './grid-tokens';
5655
'[attr.aria-activedescendant]': '_pattern.activeDescendant()',
5756
'(keydown)': '_pattern.onKeydown($event)',
5857
'(pointerdown)': '_pattern.onPointerdown($event)',
59-
'(pointerup)': '_pattern.onPointerup($event)',
6058
'(focusin)': '_pattern.onFocusIn($event)',
6159
'(focusout)': '_pattern.onFocusOut($event)',
6260
},
@@ -122,9 +120,6 @@ export class Grid {
122120
*/
123121
readonly selectionMode = input<'follow' | 'explicit'>('follow');
124122

125-
/** Whether enable range selections (with modifier keys or dragging). */
126-
readonly enableRangeSelection = input(false, {transform: booleanAttribute});
127-
128123
/** The UI pattern for the grid. */
129124
readonly _pattern = new GridPattern({
130125
...this,
@@ -134,22 +129,6 @@ export class Grid {
134129
});
135130

136131
constructor() {
137-
const ngZone = inject(NgZone);
138-
139-
// Since `pointermove` fires on each pixel, we need to
140-
// be careful not to hit the zone unless it's necessary.
141-
ngZone.runOutsideAngular(() => {
142-
this.element.addEventListener(
143-
'pointermove',
144-
event => {
145-
if (this._pattern.acceptsPointerMove()) {
146-
ngZone.run(() => this._pattern.onPointermove(event));
147-
}
148-
},
149-
{passive: true},
150-
);
151-
});
152-
153132
afterRenderEffect(() => this._pattern.setDefaultStateEffect());
154133
afterRenderEffect(() => this._pattern.resetStateEffect());
155134
afterRenderEffect(() => this._pattern.resetFocusEffect());

src/aria/private/grid/grid.spec.ts

Lines changed: 0 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,6 @@ const end = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 35, 'End', m
3737
const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods);
3838
const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods);
3939
const escape = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 27, 'Escape', mods);
40-
const shiftUp = () => up({shift: true});
41-
const shiftDown = () => down({shift: true});
42-
const shiftLeft = () => left({shift: true});
43-
const shiftRight = () => right({shift: true});
44-
const shiftHome = () => home({shift: true});
45-
const shiftEnd = () => end({shift: true});
4640

4741
function createClickEvent(element: HTMLElement, mods?: ModifierKeys): PointerEvent {
4842
return {
@@ -135,7 +129,6 @@ function getDefaultGridInputs(): TestGridInputs {
135129
enableSelection: signal(false),
136130
multi: signal(false),
137131
selectionMode: signal('follow'),
138-
enableRangeSelection: signal(false),
139132
getCell: () => undefined,
140133
focusMode: signal('roving'),
141134
disabled: signal(false),
@@ -487,7 +480,6 @@ describe('Grid', () => {
487480

488481
it('should select all on Ctrl+A', () => {
489482
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
490-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
491483
grid.onKeydown(a({control: true}));
492484
expect(
493485
grid
@@ -499,7 +491,6 @@ describe('Grid', () => {
499491

500492
it('should select row on Shift+Space', () => {
501493
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
502-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
503494
const gridCells = grid.cells();
504495
grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]);
505496
grid.onKeydown(space({shift: true}));
@@ -512,7 +503,6 @@ describe('Grid', () => {
512503

513504
it('should select column on Ctrl+Space', () => {
514505
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
515-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
516506
const gridCells = grid.cells();
517507
grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]);
518508
grid.onKeydown(space({control: true}));
@@ -523,66 +513,6 @@ describe('Grid', () => {
523513
expect(gridCells[1][1].selected()).toBe(false);
524514
});
525515
});
526-
527-
describe('Range Selection Logic', () => {
528-
let grid: GridPattern;
529-
530-
beforeEach(() => {
531-
(gridInputs.enableSelection as WritableSignalLike<boolean>).set(true);
532-
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
533-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
534-
535-
const data = [{cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}];
536-
const result = createGrid(data, gridInputs);
537-
grid = result.grid;
538-
grid.setDefaultStateEffect();
539-
});
540-
541-
it('should expand the selection range up on Shift+ArrowUp', () => {
542-
const cells = grid.cells();
543-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
544-
grid.onKeydown(shiftUp());
545-
expect(cells[1][1].selected()).toBe(true);
546-
expect(cells[0][1].selected()).toBe(true);
547-
});
548-
549-
it('should expand the selection range down on Shift+ArrowDown', () => {
550-
const cells = grid.cells();
551-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
552-
grid.onKeydown(shiftDown());
553-
expect(cells[1][1].selected()).toBe(true);
554-
expect(cells[2][1].selected()).toBe(true);
555-
});
556-
557-
it('should expand the selection range left on Shift+ArrowLeft', () => {
558-
const cells = grid.cells();
559-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
560-
grid.onKeydown(shiftLeft());
561-
expect(cells[1][1].selected()).toBe(true);
562-
expect(cells[1][0].selected()).toBe(true);
563-
});
564-
565-
it('should expand the selection range right on Shift+ArrowRight', () => {
566-
const cells = grid.cells();
567-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
568-
grid.onKeydown(shiftRight());
569-
expect(cells[1][1].selected()).toBe(true);
570-
expect(cells[1][2].selected()).toBe(true);
571-
});
572-
573-
it('should support range selection with Shift+Home/End', () => {
574-
const cells = grid.cells();
575-
grid.gridBehavior.focusBehavior.focusCell(cells[0][1]);
576-
grid.onKeydown(shiftHome());
577-
expect(cells[0][0].selected()).toBe(true);
578-
expect(cells[0][1].selected()).toBe(true);
579-
580-
grid.onKeydown(shiftEnd());
581-
expect(cells[0][0].selected()).toBe(false);
582-
expect(cells[0][1].selected()).toBe(true);
583-
expect(cells[0][2].selected()).toBe(true);
584-
});
585-
});
586516
});
587517

588518
describe('Pointer Events', () => {
@@ -630,42 +560,6 @@ describe('Grid', () => {
630560
expect(cells[0][0].selected()).toBe(true);
631561
expect(cells[0][1].selected()).toBe(true);
632562
});
633-
634-
it('should support range selection with Shift+pointerdown', () => {
635-
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
636-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
637-
const cells = grid.cells();
638-
grid.onPointerdown(createClickEvent(cells[0][0].element()));
639-
grid.onPointerdown(createClickEvent(cells[1][1].element(), {shift: true}));
640-
expect(cells[0][0].selected()).toBe(true);
641-
expect(cells[0][1].selected()).toBe(true);
642-
expect(cells[1][0].selected()).toBe(true);
643-
expect(cells[1][1].selected()).toBe(true);
644-
});
645-
});
646-
647-
describe('Range Selection Dragging', () => {
648-
beforeEach(() => {
649-
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
650-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
651-
});
652-
653-
it('should select range on pointermove', () => {
654-
const cells = grid.cells();
655-
grid.onPointerdown(createClickEvent(cells[0][0].element()));
656-
grid.onPointermove(createClickEvent(cells[0][1].element()));
657-
expect(cells[0][0].selected()).toBe(true);
658-
expect(cells[0][1].selected()).toBe(true);
659-
});
660-
661-
it('should stabilize selection on pointerup', () => {
662-
const cell = grid.cells()[0][1];
663-
grid.onPointerdown(createClickEvent(grid.cells()[0][0].element()));
664-
grid.onPointermove(createClickEvent(cell.element()));
665-
expect(grid.dragging()).toBe(true);
666-
grid.onPointerup(createClickEvent(cell.element()));
667-
expect(grid.dragging()).toBe(false);
668-
});
669563
});
670564
});
671565

0 commit comments

Comments
 (0)