Skip to content

Commit 7bbc244

Browse files
committed
refactor(aria/grid): remove range selection to reduce computational overhead
1 parent 046e1a2 commit 7bbc244

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
}
@@ -60,10 +59,10 @@ export class GridCell {
6059
export class GridCellWidget {
6160
constructor();
6261
activate(): void;
63-
readonly activated: _angular_core.OutputEmitterRef<FocusEvent | KeyboardEvent | undefined>;
62+
readonly activated: _angular_core.OutputEmitterRef<KeyboardEvent | FocusEvent | undefined>;
6463
readonly active: Signal<boolean>;
6564
deactivate(): void;
66-
readonly deactivated: _angular_core.OutputEmitterRef<FocusEvent | KeyboardEvent | undefined>;
65+
readonly deactivated: _angular_core.OutputEmitterRef<KeyboardEvent | FocusEvent | undefined>;
6766
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
6867
readonly element: HTMLElement;
6968
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
@@ -377,7 +377,6 @@ export class GridCellWidgetPattern implements ListNavigationItem {
377377
// @public
378378
export interface GridInputs extends Omit<GridInputs$1<GridCellPattern>, 'cells'> {
379379
element: SignalLike<HTMLElement>;
380-
enableRangeSelection: SignalLike<boolean>;
381380
enableSelection: SignalLike<boolean>;
382381
getCell: (e: Element | null) => GridCellPattern | undefined;
383382
multi: SignalLike<boolean>;
@@ -389,7 +388,6 @@ export interface GridInputs extends Omit<GridInputs$1<GridCellPattern>, 'cells'>
389388
// @public
390389
export class GridPattern {
391390
constructor(inputs: GridInputs);
392-
readonly acceptsPointerMove: SignalLike<boolean>;
393391
readonly activeCell: SignalLike<GridCellPattern | undefined>;
394392
readonly activeDescendant: SignalLike<string | undefined>;
395393
readonly anchorCell: SignalLike<GridCellPattern | undefined>;
@@ -409,11 +407,8 @@ export class GridPattern {
409407
onFocusOut(event: FocusEvent): void;
410408
onKeydown(event: KeyboardEvent): void;
411409
onPointerdown(event: PointerEvent): void;
412-
onPointermove(event: PointerEvent): void;
413-
onPointerup(event: PointerEvent): void;
414410
readonly pauseNavigation: SignalLike<boolean>;
415411
readonly pointerdown: SignalLike<PointerEventManager<PointerEvent>>;
416-
readonly pointerup: SignalLike<PointerEventManager<PointerEvent>>;
417412
readonly prevColKey: SignalLike<"ArrowRight" | "ArrowLeft">;
418413
resetFocusEffect(): void;
419414
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 {
@@ -141,7 +135,6 @@ function getDefaultGridInputs(): TestGridInputs {
141135
enableSelection: signal(false),
142136
multi: signal(false),
143137
selectionMode: signal('follow'),
144-
enableRangeSelection: signal(false),
145138
getCell: () => undefined,
146139
focusMode: signal('roving'),
147140
disabled: signal(false),
@@ -602,7 +595,6 @@ describe('Grid', () => {
602595

603596
it('should select all on Ctrl+A', () => {
604597
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
605-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
606598
grid.onKeydown(a({control: true}));
607599
expect(
608600
grid
@@ -614,7 +606,6 @@ describe('Grid', () => {
614606

615607
it('should select row on Shift+Space', () => {
616608
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
617-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
618609
const gridCells = grid.cells();
619610
grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]);
620611
grid.onKeydown(space({shift: true}));
@@ -627,7 +618,6 @@ describe('Grid', () => {
627618

628619
it('should select column on Ctrl+Space', () => {
629620
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
630-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
631621
const gridCells = grid.cells();
632622
grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]);
633623
grid.onKeydown(space({control: true}));
@@ -638,66 +628,6 @@ describe('Grid', () => {
638628
expect(gridCells[1][1].selected()).toBe(false);
639629
});
640630
});
641-
642-
describe('Range Selection Logic', () => {
643-
let grid: GridPattern;
644-
645-
beforeEach(() => {
646-
(gridInputs.enableSelection as WritableSignalLike<boolean>).set(true);
647-
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
648-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
649-
650-
const data = [{cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}];
651-
const result = createGrid(data, gridInputs);
652-
grid = result.grid;
653-
grid.setDefaultStateEffect();
654-
});
655-
656-
it('should expand the selection range up on Shift+ArrowUp', () => {
657-
const cells = grid.cells();
658-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
659-
grid.onKeydown(shiftUp());
660-
expect(cells[1][1].selected()).toBe(true);
661-
expect(cells[0][1].selected()).toBe(true);
662-
});
663-
664-
it('should expand the selection range down on Shift+ArrowDown', () => {
665-
const cells = grid.cells();
666-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
667-
grid.onKeydown(shiftDown());
668-
expect(cells[1][1].selected()).toBe(true);
669-
expect(cells[2][1].selected()).toBe(true);
670-
});
671-
672-
it('should expand the selection range left on Shift+ArrowLeft', () => {
673-
const cells = grid.cells();
674-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
675-
grid.onKeydown(shiftLeft());
676-
expect(cells[1][1].selected()).toBe(true);
677-
expect(cells[1][0].selected()).toBe(true);
678-
});
679-
680-
it('should expand the selection range right on Shift+ArrowRight', () => {
681-
const cells = grid.cells();
682-
grid.gridBehavior.focusBehavior.focusCell(cells[1][1]);
683-
grid.onKeydown(shiftRight());
684-
expect(cells[1][1].selected()).toBe(true);
685-
expect(cells[1][2].selected()).toBe(true);
686-
});
687-
688-
it('should support range selection with Shift+Home/End', () => {
689-
const cells = grid.cells();
690-
grid.gridBehavior.focusBehavior.focusCell(cells[0][1]);
691-
grid.onKeydown(shiftHome());
692-
expect(cells[0][0].selected()).toBe(true);
693-
expect(cells[0][1].selected()).toBe(true);
694-
695-
grid.onKeydown(shiftEnd());
696-
expect(cells[0][0].selected()).toBe(false);
697-
expect(cells[0][1].selected()).toBe(true);
698-
expect(cells[0][2].selected()).toBe(true);
699-
});
700-
});
701631
});
702632

703633
describe('Pointer Events', () => {
@@ -745,42 +675,6 @@ describe('Grid', () => {
745675
expect(cells[0][0].selected()).toBe(true);
746676
expect(cells[0][1].selected()).toBe(true);
747677
});
748-
749-
it('should support range selection with Shift+pointerdown', () => {
750-
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
751-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
752-
const cells = grid.cells();
753-
grid.onPointerdown(createClickEvent(cells[0][0].element()));
754-
grid.onPointerdown(createClickEvent(cells[1][1].element(), {shift: true}));
755-
expect(cells[0][0].selected()).toBe(true);
756-
expect(cells[0][1].selected()).toBe(true);
757-
expect(cells[1][0].selected()).toBe(true);
758-
expect(cells[1][1].selected()).toBe(true);
759-
});
760-
});
761-
762-
describe('Range Selection Dragging', () => {
763-
beforeEach(() => {
764-
(gridInputs.multi as WritableSignalLike<boolean>).set(true);
765-
(gridInputs.enableRangeSelection as WritableSignalLike<boolean>).set(true);
766-
});
767-
768-
it('should select range on pointermove', () => {
769-
const cells = grid.cells();
770-
grid.onPointerdown(createClickEvent(cells[0][0].element()));
771-
grid.onPointermove(createClickEvent(cells[0][1].element()));
772-
expect(cells[0][0].selected()).toBe(true);
773-
expect(cells[0][1].selected()).toBe(true);
774-
});
775-
776-
it('should stabilize selection on pointerup', () => {
777-
const cell = grid.cells()[0][1];
778-
grid.onPointerdown(createClickEvent(grid.cells()[0][0].element()));
779-
grid.onPointermove(createClickEvent(cell.element()));
780-
expect(grid.dragging()).toBe(true);
781-
grid.onPointerup(createClickEvent(cell.element()));
782-
expect(grid.dragging()).toBe(false);
783-
});
784678
});
785679
});
786680

0 commit comments

Comments
 (0)