diff --git a/goldens/aria/grid/index.api.md b/goldens/aria/grid/index.api.md index 4eb62d5cb7d4..4a8fb17c07dc 100644 --- a/goldens/aria/grid/index.api.md +++ b/goldens/aria/grid/index.api.md @@ -15,7 +15,6 @@ export class Grid { readonly colWrap: _angular_core.InputSignal<"continuous" | "loop" | "nowrap">; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; - readonly enableRangeSelection: _angular_core.InputSignalWithTransform; readonly enableSelection: _angular_core.InputSignalWithTransform; readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">; readonly multi: _angular_core.InputSignalWithTransform; @@ -25,7 +24,7 @@ export class Grid { readonly softDisabled: _angular_core.InputSignalWithTransform; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } @@ -60,10 +59,10 @@ export class GridCell { export class GridCellWidget { constructor(); activate(): void; - readonly activated: _angular_core.OutputEmitterRef; + readonly activated: _angular_core.OutputEmitterRef; readonly active: Signal; deactivate(): void; - readonly deactivated: _angular_core.OutputEmitterRef; + readonly deactivated: _angular_core.OutputEmitterRef; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; readonly focusTarget: _angular_core.InputSignal | HTMLElement | undefined>; diff --git a/goldens/aria/private/index.api.md b/goldens/aria/private/index.api.md index f484be549c6c..182cee5cb1f5 100644 --- a/goldens/aria/private/index.api.md +++ b/goldens/aria/private/index.api.md @@ -377,7 +377,6 @@ export class GridCellWidgetPattern implements ListNavigationItem { // @public export interface GridInputs extends Omit, 'cells'> { element: SignalLike; - enableRangeSelection: SignalLike; enableSelection: SignalLike; getCell: (e: Element | null) => GridCellPattern | undefined; multi: SignalLike; @@ -389,7 +388,6 @@ export interface GridInputs extends Omit, 'cells'> // @public export class GridPattern { constructor(inputs: GridInputs); - readonly acceptsPointerMove: SignalLike; readonly activeCell: SignalLike; readonly activeDescendant: SignalLike; readonly anchorCell: SignalLike; @@ -409,11 +407,8 @@ export class GridPattern { onFocusOut(event: FocusEvent): void; onKeydown(event: KeyboardEvent): void; onPointerdown(event: PointerEvent): void; - onPointermove(event: PointerEvent): void; - onPointerup(event: PointerEvent): void; readonly pauseNavigation: SignalLike; readonly pointerdown: SignalLike>; - readonly pointerup: SignalLike>; readonly prevColKey: SignalLike<"ArrowRight" | "ArrowLeft">; resetFocusEffect(): void; resetStateEffect(): void; diff --git a/src/aria/grid/grid.spec.ts b/src/aria/grid/grid.spec.ts index 7c2980d69c96..c3d4c3c074f1 100644 --- a/src/aria/grid/grid.spec.ts +++ b/src/aria/grid/grid.spec.ts @@ -63,16 +63,6 @@ describe('Grid directives', () => { fixture.detectChanges(); }; - const pointerMove = (target: HTMLElement | Window, eventInit: PointerEventInit = {}) => { - target.dispatchEvent(new PointerEvent('pointermove', {bubbles: true, ...eventInit})); - fixture.detectChanges(); - }; - - const pointerUp = (target: HTMLElement | Window, eventInit: PointerEventInit = {}) => { - target.dispatchEvent(new PointerEvent('pointerup', {bubbles: true, ...eventInit})); - fixture.detectChanges(); - }; - const up = (modifierKeys?: ModifierKeys) => keydown('ArrowUp', modifierKeys); const down = (modifierKeys?: ModifierKeys) => keydown('ArrowDown', modifierKeys); const left = (modifierKeys?: ModifierKeys) => keydown('ArrowLeft', modifierKeys); @@ -105,7 +95,6 @@ describe('Grid directives', () => { softDisabled?: boolean; enableSelection?: boolean; selectionMode?: 'follow' | 'explicit'; - enableRangeSelection?: boolean; gridData?: RowConfig[]; }) { TestBed.resetTestingModule(); @@ -122,8 +111,6 @@ describe('Grid directives', () => { if (opts?.enableSelection !== undefined) testComponent.enableSelection.set(opts.enableSelection); if (opts?.selectionMode !== undefined) testComponent.selectionMode.set(opts.selectionMode); - if (opts?.enableRangeSelection !== undefined) - testComponent.enableRangeSelection.set(opts.enableRangeSelection); if (opts?.gridData !== undefined) { testComponent.gridData.set(opts.gridData); @@ -388,7 +375,6 @@ describe('Grid directives', () => { enableSelection: true, selectionMode: 'explicit', multi: true, - enableRangeSelection: true, }); gridInstance._pattern.setDefaultStateEffect(); fixture.detectChanges(); @@ -488,7 +474,6 @@ describe('Grid directives', () => { enableSelection: true, selectionMode: 'explicit', multi: true, - enableRangeSelection: true, }); gridInstance._pattern.setDefaultStateEffect(); fixture.detectChanges(); @@ -503,41 +488,6 @@ describe('Grid directives', () => { expect(cell.getAttribute('aria-selected')).toBe('true'); expect(getActiveCellId()).toBe('c1-1'); }); - - it('should expand selection on pointermove while dragging without changing active cell', () => { - const startCell = gridElement.querySelector('#c0-0') as HTMLElement; - const dragCell = gridElement.querySelector('#c1-1') as HTMLElement; - - pointerDown(startCell); - pointerMove(dragCell); - - expect(getActiveCellId()).toBe('c0-0'); - // Dragging expands selection - expect(startCell.getAttribute('aria-selected')).toBe('true'); - expect(dragCell.getAttribute('aria-selected')).toBe('true'); - }); - - it('should stop dragging on pointerup', () => { - const startCell = gridElement.querySelector('#c0-0') as HTMLElement; - const endCell = gridElement.querySelector('#c1-1') as HTMLElement; - - pointerDown(startCell); - pointerUp(gridElement); - pointerMove(endCell); - - // Active cell should still be c0-0 because dragging stopped before moving to c1-1 - expect(getActiveCellId()).toBe('c0-0'); - expect(endCell.getAttribute('aria-selected')).toBe('false'); - }); - - it('should not change active cell on pointermove outside of the grid cells', () => { - const startCell = gridElement.querySelector('#c0-0') as HTMLElement; - - pointerDown(startCell); - pointerMove(gridElement); - - expect(getActiveCellId()).toBe('c0-0'); - }); }); describe('configuration', () => { @@ -1009,8 +959,7 @@ describe('Grid directives', () => { [focusMode]="focusMode()" [softDisabled]="softDisabled()" [enableSelection]="enableSelection()" - [selectionMode]="selectionMode()" - [enableRangeSelection]="enableRangeSelection()"> + [selectionMode]="selectionMode()"> @for (row of gridData(); track $index; let rIndex = $index) { @for (cell of row.cells; track $index; let cIndex = $index) { @@ -1067,7 +1016,6 @@ class GridTestComponent { readonly softDisabled = signal(true); readonly enableSelection = signal(false); readonly selectionMode = signal<'follow' | 'explicit'>('follow'); - readonly enableRangeSelection = signal(false); readonly gridData = signal(createGridData()); onActivated = jasmine.createSpy('activated'); diff --git a/src/aria/grid/grid.ts b/src/aria/grid/grid.ts index 7799f2defb40..bb6bb34ab44b 100644 --- a/src/aria/grid/grid.ts +++ b/src/aria/grid/grid.ts @@ -15,7 +15,6 @@ import { ElementRef, inject, input, - NgZone, Signal, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; @@ -56,7 +55,6 @@ import {GRID_ROW} from './grid-tokens'; '[attr.aria-activedescendant]': '_pattern.activeDescendant()', '(keydown)': '_pattern.onKeydown($event)', '(pointerdown)': '_pattern.onPointerdown($event)', - '(pointerup)': '_pattern.onPointerup($event)', '(focusin)': '_pattern.onFocusIn($event)', '(focusout)': '_pattern.onFocusOut($event)', }, @@ -122,9 +120,6 @@ export class Grid { */ readonly selectionMode = input<'follow' | 'explicit'>('follow'); - /** Whether enable range selections (with modifier keys or dragging). */ - readonly enableRangeSelection = input(false, {transform: booleanAttribute}); - /** The UI pattern for the grid. */ readonly _pattern = new GridPattern({ ...this, @@ -134,22 +129,6 @@ export class Grid { }); constructor() { - const ngZone = inject(NgZone); - - // Since `pointermove` fires on each pixel, we need to - // be careful not to hit the zone unless it's necessary. - ngZone.runOutsideAngular(() => { - this.element.addEventListener( - 'pointermove', - event => { - if (this._pattern.acceptsPointerMove()) { - ngZone.run(() => this._pattern.onPointermove(event)); - } - }, - {passive: true}, - ); - }); - afterRenderEffect(() => this._pattern.setDefaultStateEffect()); afterRenderEffect(() => this._pattern.resetStateEffect()); afterRenderEffect(() => this._pattern.resetFocusEffect()); diff --git a/src/aria/private/grid/grid.spec.ts b/src/aria/private/grid/grid.spec.ts index 906e2261a01a..a79d8bf4793d 100644 --- a/src/aria/private/grid/grid.spec.ts +++ b/src/aria/private/grid/grid.spec.ts @@ -37,12 +37,6 @@ const end = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 35, 'End', m const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods); const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods); const escape = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 27, 'Escape', mods); -const shiftUp = () => up({shift: true}); -const shiftDown = () => down({shift: true}); -const shiftLeft = () => left({shift: true}); -const shiftRight = () => right({shift: true}); -const shiftHome = () => home({shift: true}); -const shiftEnd = () => end({shift: true}); function createClickEvent(element: HTMLElement, mods?: ModifierKeys): PointerEvent { return { @@ -141,7 +135,6 @@ function getDefaultGridInputs(): TestGridInputs { enableSelection: signal(false), multi: signal(false), selectionMode: signal('follow'), - enableRangeSelection: signal(false), getCell: () => undefined, focusMode: signal('roving'), disabled: signal(false), @@ -602,7 +595,6 @@ describe('Grid', () => { it('should select all on Ctrl+A', () => { (gridInputs.multi as WritableSignalLike).set(true); - (gridInputs.enableRangeSelection as WritableSignalLike).set(true); grid.onKeydown(a({control: true})); expect( grid @@ -614,7 +606,6 @@ describe('Grid', () => { it('should select row on Shift+Space', () => { (gridInputs.multi as WritableSignalLike).set(true); - (gridInputs.enableRangeSelection as WritableSignalLike).set(true); const gridCells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]); grid.onKeydown(space({shift: true})); @@ -627,7 +618,6 @@ describe('Grid', () => { it('should select column on Ctrl+Space', () => { (gridInputs.multi as WritableSignalLike).set(true); - (gridInputs.enableRangeSelection as WritableSignalLike).set(true); const gridCells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]); grid.onKeydown(space({control: true})); @@ -638,66 +628,6 @@ describe('Grid', () => { expect(gridCells[1][1].selected()).toBe(false); }); }); - - describe('Range Selection Logic', () => { - let grid: GridPattern; - - beforeEach(() => { - (gridInputs.enableSelection as WritableSignalLike).set(true); - (gridInputs.multi as WritableSignalLike).set(true); - (gridInputs.enableRangeSelection as WritableSignalLike).set(true); - - const data = [{cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}]; - const result = createGrid(data, gridInputs); - grid = result.grid; - grid.setDefaultStateEffect(); - }); - - it('should expand the selection range up on Shift+ArrowUp', () => { - const cells = grid.cells(); - grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); - grid.onKeydown(shiftUp()); - expect(cells[1][1].selected()).toBe(true); - expect(cells[0][1].selected()).toBe(true); - }); - - it('should expand the selection range down on Shift+ArrowDown', () => { - const cells = grid.cells(); - grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); - grid.onKeydown(shiftDown()); - expect(cells[1][1].selected()).toBe(true); - expect(cells[2][1].selected()).toBe(true); - }); - - it('should expand the selection range left on Shift+ArrowLeft', () => { - const cells = grid.cells(); - grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); - grid.onKeydown(shiftLeft()); - expect(cells[1][1].selected()).toBe(true); - expect(cells[1][0].selected()).toBe(true); - }); - - it('should expand the selection range right on Shift+ArrowRight', () => { - const cells = grid.cells(); - grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); - grid.onKeydown(shiftRight()); - expect(cells[1][1].selected()).toBe(true); - expect(cells[1][2].selected()).toBe(true); - }); - - it('should support range selection with Shift+Home/End', () => { - const cells = grid.cells(); - grid.gridBehavior.focusBehavior.focusCell(cells[0][1]); - grid.onKeydown(shiftHome()); - expect(cells[0][0].selected()).toBe(true); - expect(cells[0][1].selected()).toBe(true); - - grid.onKeydown(shiftEnd()); - expect(cells[0][0].selected()).toBe(false); - expect(cells[0][1].selected()).toBe(true); - expect(cells[0][2].selected()).toBe(true); - }); - }); }); describe('Pointer Events', () => { @@ -745,42 +675,6 @@ describe('Grid', () => { expect(cells[0][0].selected()).toBe(true); expect(cells[0][1].selected()).toBe(true); }); - - it('should support range selection with Shift+pointerdown', () => { - (gridInputs.multi as WritableSignalLike).set(true); - (gridInputs.enableRangeSelection as WritableSignalLike).set(true); - const cells = grid.cells(); - grid.onPointerdown(createClickEvent(cells[0][0].element())); - grid.onPointerdown(createClickEvent(cells[1][1].element(), {shift: true})); - expect(cells[0][0].selected()).toBe(true); - expect(cells[0][1].selected()).toBe(true); - expect(cells[1][0].selected()).toBe(true); - expect(cells[1][1].selected()).toBe(true); - }); - }); - - describe('Range Selection Dragging', () => { - beforeEach(() => { - (gridInputs.multi as WritableSignalLike).set(true); - (gridInputs.enableRangeSelection as WritableSignalLike).set(true); - }); - - it('should select range on pointermove', () => { - const cells = grid.cells(); - grid.onPointerdown(createClickEvent(cells[0][0].element())); - grid.onPointermove(createClickEvent(cells[0][1].element())); - expect(cells[0][0].selected()).toBe(true); - expect(cells[0][1].selected()).toBe(true); - }); - - it('should stabilize selection on pointerup', () => { - const cell = grid.cells()[0][1]; - grid.onPointerdown(createClickEvent(grid.cells()[0][0].element())); - grid.onPointermove(createClickEvent(cell.element())); - expect(grid.dragging()).toBe(true); - grid.onPointerup(createClickEvent(cell.element())); - expect(grid.dragging()).toBe(false); - }); }); }); diff --git a/src/aria/private/grid/grid.ts b/src/aria/private/grid/grid.ts index 1d448d8fa9d2..2f557b2e383c 100644 --- a/src/aria/private/grid/grid.ts +++ b/src/aria/private/grid/grid.ts @@ -32,9 +32,6 @@ export interface GridInputs extends Omit, 'c /** The selection strategy used by the grid. */ selectionMode: SignalLike<'follow' | 'explicit'>; - /** Whether enable range selection. */ - enableRangeSelection: SignalLike; - /** A function that returns the grid cell associated with a given element. */ getCell: (e: Element | null) => GridCellPattern | undefined; } @@ -96,16 +93,6 @@ export class GridPattern { this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight', ); - /** Whether the grid pattern is currently accepting `pointermove` events. */ - readonly acceptsPointerMove = computed(() => { - return ( - !this.disabled() && - this.inputs.enableSelection() && - this.inputs.enableRangeSelection() && - this.dragging() - ); - }); - /** The keydown event manager for the grid. */ readonly keydown = computed(() => { const manager = new KeyboardEventManager(); @@ -135,17 +122,9 @@ export class GridPattern { ); } - // Range selection handlers. - if (this.inputs.enableSelection() && this.inputs.enableRangeSelection()) { + // Explicit multiselection modifier handlers. + if (this.inputs.enableSelection() && this.inputs.multi()) { manager - .on(Modifier.Shift, 'ArrowUp', () => this.gridBehavior.up({anchor: true})) - .on(Modifier.Shift, 'ArrowDown', () => this.gridBehavior.down({anchor: true})) - .on(Modifier.Shift, this.prevColKey(), () => this.gridBehavior.left({anchor: true})) - .on(Modifier.Shift, this.nextColKey(), () => this.gridBehavior.right({anchor: true})) - .on(Modifier.Shift, 'Home', () => this.gridBehavior.firstInRow({anchor: true})) - .on(Modifier.Shift, 'End', () => this.gridBehavior.lastInRow({anchor: true})) - .on([Modifier.Ctrl | Modifier.Shift], 'Home', () => this.gridBehavior.first({anchor: true})) - .on([Modifier.Ctrl | Modifier.Shift], 'End', () => this.gridBehavior.last({anchor: true})) .on([Modifier.Ctrl, Modifier.Meta], 'A', () => { if (this.gridBehavior.allSelected()) { this.gridBehavior.deselectAll(); @@ -185,10 +164,6 @@ export class GridPattern { toggleOne: this.inputs.selectionMode() === 'explicit' && !this.inputs.multi(), toggle: this.inputs.selectionMode() === 'explicit' && this.inputs.multi(), }); - - if (this.inputs.multi() && this.inputs.enableRangeSelection()) { - this.dragging.set(true); - } }); // Selection with modifier keys. @@ -198,40 +173,13 @@ export class GridPattern { if (!cell || !this.gridBehavior.focusBehavior.isFocusable(cell)) return; this.gridBehavior.gotoCell(cell, {toggle: true}); - - if (this.inputs.enableRangeSelection()) { - this.dragging.set(true); - } }); - - if (this.inputs.enableRangeSelection()) { - manager.on(Modifier.Shift, e => { - const cell = this.inputs.getCell(e.target as Element); - if (!cell) return; - - this.gridBehavior.gotoCell(cell, {anchor: true}); - this.dragging.set(true); - }); - } } } return manager; }); - /** The pointerup event manager for the grid. */ - readonly pointerup = computed(() => { - const manager = new PointerEventManager(); - - if (this.inputs.enableSelection() && this.inputs.enableRangeSelection()) { - manager.on([Modifier.Shift, Modifier.Ctrl, Modifier.Meta, Modifier.None], () => { - this.dragging.set(false); - }); - } - - return manager; - }); - /** Indicates maybe the losing focus is caused by row/cell deletion. */ private readonly _maybeDeletion = signal(false); @@ -265,33 +213,11 @@ export class GridPattern { this.pointerdown().handle(event); } - /** Handles pointermove events on the grid. */ - onPointermove(event: PointerEvent) { - if (this.acceptsPointerMove()) { - const cell = this.inputs.getCell(event.target as Element); - - // Dragging anchor. - if (cell !== undefined) { - this.gridBehavior.gotoCell(cell, {anchor: true}); - } - } - } - - /** Handles pointerup events on the grid. */ - onPointerup(event: PointerEvent) { - if (this.disabled()) return; - - this.pointerup().handle(event); - } - /** Handles focusin events on the grid. */ onFocusIn(event: FocusEvent) { this.isFocused.set(true); this.hasBeenInteracted.set(true); - // Skip if in the middle of range selection. - if (this.dragging()) return; - // Cell that receives focus. const cell = this.inputs.getCell(event.target as Element | null); if (!cell || !this.gridBehavior.focusBehavior.isFocusable(cell)) return; diff --git a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html index c56e1e022c87..a6111657fb34 100644 --- a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html +++ b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html @@ -3,7 +3,6 @@ Soft Disabled Enable Selection Multi - Range Selection @for (row of gridData; track row) { @@ -117,15 +115,10 @@ @if (this.selectionMode === 'follow') {
  • Selection change on navigation (follow focus)
  • } - @if (multi.value && enableRangeSelection.value) { -
  • Shift + Arrow: expand selection
  • + @if (multi.value) {
  • Ctrl + A: select all or deselect all (if all selected)
  • Shift + Space: select a row
  • Ctrl + Space: select a col
  • -
  • Shift + Home: range select from first cell in the row
  • -
  • Shift + End: range select to last cell in the row
  • -
  • Shift + Ctrl + Home: range select from very first cell
  • -
  • Shift + Ctrl + End: range select to very last cell
  • } @@ -140,16 +133,6 @@
  • Ctrl + Click: add/remove a cell
  • } } - @if (multi.value && enableRangeSelection.value) { - @if (this.selectionMode === 'explicit') { -
  • Drag: add a new range selection (explicit)
  • - } - @if (this.selectionMode === 'follow') { -
  • Drag: start a new range selection (follow focus)
  • - } -
  • Shift + Drag: update the current range selection
  • -
  • Ctrl + Drag: add a new range selection
  • - } } diff --git a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts index 59611bda697c..f4734ff71977 100644 --- a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts +++ b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts @@ -103,7 +103,6 @@ export class GridConfigurableExample { softDisabled = new FormControl(false, {nonNullable: true}); enableSelection = new FormControl(false, {nonNullable: true}); multi = new FormControl(false, {nonNullable: true}); - enableRangeSelection = new FormControl(false, {nonNullable: true}); gridData: Cell[][] = generateValidGrid( 10,