-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Expand file tree
/
Copy pathgrid-cell-widget.ts
More file actions
139 lines (120 loc) · 3.83 KB
/
grid-cell-widget.ts
File metadata and controls
139 lines (120 loc) · 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {_IdGenerator} from '@angular/cdk/a11y';
import {
afterRenderEffect,
booleanAttribute,
computed,
Directive,
ElementRef,
inject,
input,
output,
Signal,
} from '@angular/core';
import {GridCellWidgetPattern} from '../private';
import {GRID_CELL} from './grid-tokens';
/**
* Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to
* interact with the widget.
*
* When the user interacts with the widget (e.g., by typing in an input or opening a menu), grid
* navigation is temporarily suspended to allow the widget to handle keyboard
* events.
*
* ```html
* <td ngGridCell>
* <button ngGridCellWidget>Click Me</button>
* </td>
* ```
*
* @developerPreview 21.0
*
* @see [Grid](guide/aria/grid)
*/
@Directive({
selector: '[ngGridCellWidget]',
exportAs: 'ngGridCellWidget',
host: {
'[attr.data-active]': 'active()',
'[attr.data-active-control]': 'isActivated() ? "widget" : "cell"',
'[tabindex]': '_tabIndex()',
'[attr.id]': 'id()',
},
})
export class GridCellWidget {
/** A reference to the host element. */
private readonly _elementRef = inject(ElementRef);
/** A reference to the host element. */
readonly element = this._elementRef.nativeElement as HTMLElement;
/** Whether the widget is currently active (focused). */
readonly active = computed(() => this._pattern.active());
/** The parent cell. */
private readonly _cell = inject(GRID_CELL);
/** A unique identifier for the widget. */
readonly id = input(inject(_IdGenerator).getId('ng-grid-cell-widget-', true));
/** The type of widget, which determines how it is activated. */
readonly widgetType = input<'simple' | 'complex' | 'editable'>('simple');
/** Whether the widget is disabled. */
readonly disabled = input(false, {transform: booleanAttribute});
/** The target that will receive focus instead of the widget. */
readonly focusTarget = input<ElementRef | HTMLElement | undefined>();
/** Emits when the widget is activated. */
readonly activated = output<KeyboardEvent | FocusEvent | undefined>();
/** Emits when the widget is deactivated. */
readonly deactivated = output<KeyboardEvent | FocusEvent | undefined>();
/** The tabindex override. */
readonly tabindex = input<number | undefined>();
/**
* The tabindex value set to the element.
* If a focus target exists then return -1. Unless an override.
*/
protected readonly _tabIndex: Signal<number> = computed(
() => this.tabindex() ?? (this.focusTarget() ? -1 : this._pattern.tabIndex()),
);
/** The UI pattern for the grid cell widget. */
readonly _pattern = new GridCellWidgetPattern({
...this,
element: () => this.element,
cell: () => this._cell._pattern,
focusTarget: computed(() => {
const target = this.focusTarget();
return target instanceof ElementRef ? target.nativeElement : target;
}),
});
/** Whether the widget is activated. */
get isActivated(): Signal<boolean> {
return computed(() => this._pattern.isActivated());
}
constructor() {
afterRenderEffect({
read: () => {
const activateEvent = this._pattern.lastActivateEvent();
if (activateEvent) {
this.activated.emit(activateEvent);
}
},
});
afterRenderEffect({
read: () => {
const deactivateEvent = this._pattern.lastDeactivateEvent();
if (deactivateEvent) {
this.deactivated.emit(deactivateEvent);
}
},
});
}
/** Activates the widget. */
activate(): void {
this._pattern.activate();
}
/** Deactivates the widget. */
deactivate(): void {
this._pattern.deactivate();
}
}