@angular/cdk 21.0.0-next.8 → 21.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_adev_assets/cdk_drag_drop.json +13 -12
- package/_adev_assets/cdk_testing.json +15 -25
- package/_adev_assets/cdk_testing_protractor.json +1 -1
- package/_adev_assets/cdk_testing_selenium_webdriver.json +1 -1
- package/_adev_assets/cdk_testing_testbed.json +1 -1
- package/fesm2022/_a11y-module-chunk.mjs +755 -869
- package/fesm2022/_a11y-module-chunk.mjs.map +1 -1
- package/fesm2022/_activedescendant-key-manager-chunk.mjs +8 -8
- package/fesm2022/_activedescendant-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_array-chunk.mjs +1 -1
- package/fesm2022/_array-chunk.mjs.map +1 -1
- package/fesm2022/_breakpoints-observer-chunk.mjs +149 -152
- package/fesm2022/_breakpoints-observer-chunk.mjs.map +1 -1
- package/fesm2022/_css-pixel-value-chunk.mjs +4 -5
- package/fesm2022/_css-pixel-value-chunk.mjs.map +1 -1
- package/fesm2022/_data-source-chunk.mjs +2 -8
- package/fesm2022/_data-source-chunk.mjs.map +1 -1
- package/fesm2022/_directionality-chunk.mjs +54 -54
- package/fesm2022/_directionality-chunk.mjs.map +1 -1
- package/fesm2022/_dispose-view-repeater-strategy-chunk.mjs +25 -36
- package/fesm2022/_dispose-view-repeater-strategy-chunk.mjs.map +1 -1
- package/fesm2022/_element-chunk.mjs +6 -17
- package/fesm2022/_element-chunk.mjs.map +1 -1
- package/fesm2022/_fake-event-detection-chunk.mjs +3 -17
- package/fesm2022/_fake-event-detection-chunk.mjs.map +1 -1
- package/fesm2022/_focus-key-manager-chunk.mjs +10 -14
- package/fesm2022/_focus-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_focus-monitor-chunk.mjs +376 -566
- package/fesm2022/_focus-monitor-chunk.mjs.map +1 -1
- package/fesm2022/_id-generator-chunk.mjs +36 -27
- package/fesm2022/_id-generator-chunk.mjs.map +1 -1
- package/fesm2022/_keycodes-chunk.mjs +9 -9
- package/fesm2022/_keycodes-chunk.mjs.map +1 -1
- package/fesm2022/_list-key-manager-chunk.mjs +248 -336
- package/fesm2022/_list-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_overlay-module-chunk.mjs +2534 -2948
- package/fesm2022/_overlay-module-chunk.mjs.map +1 -1
- package/fesm2022/_passive-listeners-chunk.mjs +10 -22
- package/fesm2022/_passive-listeners-chunk.mjs.map +1 -1
- package/fesm2022/_platform-chunk.mjs +42 -65
- package/fesm2022/_platform-chunk.mjs.map +1 -1
- package/fesm2022/_recycle-view-repeater-strategy-chunk.mjs +78 -134
- package/fesm2022/_recycle-view-repeater-strategy-chunk.mjs.map +1 -1
- package/fesm2022/_scrolling-chunk.mjs +44 -85
- package/fesm2022/_scrolling-chunk.mjs.map +1 -1
- package/fesm2022/_selection-model-chunk.mjs +138 -209
- package/fesm2022/_selection-model-chunk.mjs.map +1 -1
- package/fesm2022/_shadow-dom-chunk.mjs +21 -35
- package/fesm2022/_shadow-dom-chunk.mjs.map +1 -1
- package/fesm2022/_style-loader-chunk.mjs +50 -37
- package/fesm2022/_style-loader-chunk.mjs.map +1 -1
- package/fesm2022/_test-environment-chunk.mjs +2 -14
- package/fesm2022/_test-environment-chunk.mjs.map +1 -1
- package/fesm2022/_tree-key-manager-chunk.mjs +229 -308
- package/fesm2022/_tree-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_typeahead-chunk.mjs +52 -74
- package/fesm2022/_typeahead-chunk.mjs.map +1 -1
- package/fesm2022/_unique-selection-dispatcher-chunk.mjs +43 -40
- package/fesm2022/_unique-selection-dispatcher-chunk.mjs.map +1 -1
- package/fesm2022/a11y.mjs +351 -449
- package/fesm2022/a11y.mjs.map +1 -1
- package/fesm2022/accordion.mjs +254 -192
- package/fesm2022/accordion.mjs.map +1 -1
- package/fesm2022/bidi.mjs +121 -64
- package/fesm2022/bidi.mjs.map +1 -1
- package/fesm2022/cdk.mjs +1 -2
- package/fesm2022/cdk.mjs.map +1 -1
- package/fesm2022/clipboard.mjs +208 -186
- package/fesm2022/clipboard.mjs.map +1 -1
- package/fesm2022/coercion-private.mjs +4 -8
- package/fesm2022/coercion-private.mjs.map +1 -1
- package/fesm2022/coercion.mjs +11 -29
- package/fesm2022/coercion.mjs.map +1 -1
- package/fesm2022/dialog.mjs +660 -808
- package/fesm2022/dialog.mjs.map +1 -1
- package/fesm2022/drag-drop.mjs +3347 -4286
- package/fesm2022/drag-drop.mjs.map +1 -1
- package/fesm2022/keycodes.mjs +4 -8
- package/fesm2022/keycodes.mjs.map +1 -1
- package/fesm2022/layout.mjs +44 -26
- package/fesm2022/layout.mjs.map +1 -1
- package/fesm2022/listbox.mjs +841 -895
- package/fesm2022/listbox.mjs.map +1 -1
- package/fesm2022/menu.mjs +1942 -1858
- package/fesm2022/menu.mjs.map +1 -1
- package/fesm2022/observers-private.mjs +88 -106
- package/fesm2022/observers-private.mjs.map +1 -1
- package/fesm2022/observers.mjs +262 -184
- package/fesm2022/observers.mjs.map +1 -1
- package/fesm2022/overlay.mjs +72 -68
- package/fesm2022/overlay.mjs.map +1 -1
- package/fesm2022/platform.mjs +43 -54
- package/fesm2022/platform.mjs.map +1 -1
- package/fesm2022/portal.mjs +402 -560
- package/fesm2022/portal.mjs.map +1 -1
- package/fesm2022/private.mjs +38 -10
- package/fesm2022/private.mjs.map +1 -1
- package/fesm2022/scrolling.mjs +1323 -1400
- package/fesm2022/scrolling.mjs.map +1 -1
- package/fesm2022/stepper.mjs +758 -590
- package/fesm2022/stepper.mjs.map +1 -1
- package/fesm2022/table.mjs +2327 -2319
- package/fesm2022/table.mjs.map +1 -1
- package/fesm2022/testing-selenium-webdriver.mjs +252 -325
- package/fesm2022/testing-selenium-webdriver.mjs.map +1 -1
- package/fesm2022/testing-testbed.mjs +592 -709
- package/fesm2022/testing-testbed.mjs.map +1 -1
- package/fesm2022/testing.mjs +368 -889
- package/fesm2022/testing.mjs.map +1 -1
- package/fesm2022/text-field.mjs +459 -388
- package/fesm2022/text-field.mjs.map +1 -1
- package/fesm2022/tree.mjs +1483 -1731
- package/fesm2022/tree.mjs.map +1 -1
- package/overlay/_index.scss +28 -0
- package/overlay-prebuilt.css +1 -1
- package/package.json +1 -1
- package/schematics/ng-add/index.js +1 -1
- package/types/_harness-environment-chunk.d.ts +1 -2
- package/types/_overlay-module-chunk.d.ts +59 -7
- package/types/_portal-directives-chunk.d.ts +2 -18
- package/types/accordion.d.ts +3 -1
- package/types/dialog.d.ts +1 -1
- package/types/overlay.d.ts +6 -2
- package/types/portal.d.ts +1 -1
|
@@ -9,614 +9,424 @@ import { Platform } from './_platform-chunk.mjs';
|
|
|
9
9
|
import { normalizePassiveListenerOptions } from './_passive-listeners-chunk.mjs';
|
|
10
10
|
import { coerceElement } from './_element-chunk.mjs';
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Injectable options for the InputModalityDetector. These are shallowly merged with the default
|
|
14
|
-
* options.
|
|
15
|
-
*/
|
|
16
12
|
const INPUT_MODALITY_DETECTOR_OPTIONS = new InjectionToken('cdk-input-modality-detector-options');
|
|
17
|
-
/**
|
|
18
|
-
* Default options for the InputModalityDetector.
|
|
19
|
-
*
|
|
20
|
-
* Modifier keys are ignored by default (i.e. when pressed won't cause the service to detect
|
|
21
|
-
* keyboard input modality) for two reasons:
|
|
22
|
-
*
|
|
23
|
-
* 1. Modifier keys are commonly used with mouse to perform actions such as 'right click' or 'open
|
|
24
|
-
* in new tab', and are thus less representative of actual keyboard interaction.
|
|
25
|
-
* 2. VoiceOver triggers some keyboard events when linearly navigating with Control + Option (but
|
|
26
|
-
* confusingly not with Caps Lock). Thus, to have parity with other screen readers, we ignore
|
|
27
|
-
* these keys so as to not update the input modality.
|
|
28
|
-
*
|
|
29
|
-
* Note that we do not by default ignore the right Meta key on Safari because it has the same key
|
|
30
|
-
* code as the ContextMenu key on other browsers. When we switch to using event.key, we can
|
|
31
|
-
* distinguish between the two.
|
|
32
|
-
*/
|
|
33
13
|
const INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS = {
|
|
34
|
-
|
|
14
|
+
ignoreKeys: [ALT, CONTROL, MAC_META, META, SHIFT]
|
|
35
15
|
};
|
|
36
|
-
/**
|
|
37
|
-
* The amount of time needed to pass after a touchstart event in order for a subsequent mousedown
|
|
38
|
-
* event to be attributed as mouse and not touch.
|
|
39
|
-
*
|
|
40
|
-
* This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found
|
|
41
|
-
* that a value of around 650ms seems appropriate.
|
|
42
|
-
*/
|
|
43
16
|
const TOUCH_BUFFER_MS = 650;
|
|
44
|
-
/**
|
|
45
|
-
* Event listener options that enable capturing and also mark the listener as passive if the browser
|
|
46
|
-
* supports it.
|
|
47
|
-
*/
|
|
48
17
|
const modalityEventListenerOptions = {
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
passive: true,
|
|
19
|
+
capture: true
|
|
51
20
|
};
|
|
52
|
-
/**
|
|
53
|
-
* Service that detects the user's input modality.
|
|
54
|
-
*
|
|
55
|
-
* This service does not update the input modality when a user navigates with a screen reader
|
|
56
|
-
* (e.g. linear navigation with VoiceOver, object navigation / browse mode with NVDA, virtual PC
|
|
57
|
-
* cursor mode with JAWS). This is in part due to technical limitations (i.e. keyboard events do not
|
|
58
|
-
* fire as expected in these modes) but is also arguably the correct behavior. Navigating with a
|
|
59
|
-
* screen reader is akin to visually scanning a page, and should not be interpreted as actual user
|
|
60
|
-
* input interaction.
|
|
61
|
-
*
|
|
62
|
-
* When a user is not navigating but *interacting* with a screen reader, this service attempts to
|
|
63
|
-
* update the input modality to keyboard, but in general this service's behavior is largely
|
|
64
|
-
* undefined.
|
|
65
|
-
*/
|
|
66
21
|
class InputModalityDetector {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
22
|
+
_platform = inject(Platform);
|
|
23
|
+
_listenerCleanups;
|
|
24
|
+
modalityDetected;
|
|
25
|
+
modalityChanged;
|
|
26
|
+
get mostRecentModality() {
|
|
27
|
+
return this._modality.value;
|
|
28
|
+
}
|
|
29
|
+
_mostRecentTarget = null;
|
|
30
|
+
_modality = new BehaviorSubject(null);
|
|
31
|
+
_options;
|
|
32
|
+
_lastTouchMs = 0;
|
|
33
|
+
_onKeydown = event => {
|
|
34
|
+
if (this._options?.ignoreKeys?.some(keyCode => keyCode === event.keyCode)) {
|
|
35
|
+
return;
|
|
76
36
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
_modality = new BehaviorSubject(null);
|
|
84
|
-
/** Options for this InputModalityDetector. */
|
|
85
|
-
_options;
|
|
86
|
-
/**
|
|
87
|
-
* The timestamp of the last touch input modality. Used to determine whether mousedown events
|
|
88
|
-
* should be attributed to mouse or touch.
|
|
89
|
-
*/
|
|
90
|
-
_lastTouchMs = 0;
|
|
91
|
-
/**
|
|
92
|
-
* Handles keydown events. Must be an arrow function in order to preserve the context when it gets
|
|
93
|
-
* bound.
|
|
94
|
-
*/
|
|
95
|
-
_onKeydown = (event) => {
|
|
96
|
-
// If this is one of the keys we should ignore, then ignore it and don't update the input
|
|
97
|
-
// modality to keyboard.
|
|
98
|
-
if (this._options?.ignoreKeys?.some(keyCode => keyCode === event.keyCode)) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
this._modality.next('keyboard');
|
|
102
|
-
this._mostRecentTarget = _getEventTarget(event);
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* Handles mousedown events. Must be an arrow function in order to preserve the context when it
|
|
106
|
-
* gets bound.
|
|
107
|
-
*/
|
|
108
|
-
_onMousedown = (event) => {
|
|
109
|
-
// Touches trigger both touch and mouse events, so we need to distinguish between mouse events
|
|
110
|
-
// that were triggered via mouse vs touch. To do so, check if the mouse event occurs closely
|
|
111
|
-
// after the previous touch event.
|
|
112
|
-
if (Date.now() - this._lastTouchMs < TOUCH_BUFFER_MS) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
// Fake mousedown events are fired by some screen readers when controls are activated by the
|
|
116
|
-
// screen reader. Attribute them to keyboard input modality.
|
|
117
|
-
this._modality.next(isFakeMousedownFromScreenReader(event) ? 'keyboard' : 'mouse');
|
|
118
|
-
this._mostRecentTarget = _getEventTarget(event);
|
|
119
|
-
};
|
|
120
|
-
/**
|
|
121
|
-
* Handles touchstart events. Must be an arrow function in order to preserve the context when it
|
|
122
|
-
* gets bound.
|
|
123
|
-
*/
|
|
124
|
-
_onTouchstart = (event) => {
|
|
125
|
-
// Same scenario as mentioned in _onMousedown, but on touch screen devices, fake touchstart
|
|
126
|
-
// events are fired. Again, attribute to keyboard input modality.
|
|
127
|
-
if (isFakeTouchstartFromScreenReader(event)) {
|
|
128
|
-
this._modality.next('keyboard');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// Store the timestamp of this touch event, as it's used to distinguish between mouse events
|
|
132
|
-
// triggered via mouse vs touch.
|
|
133
|
-
this._lastTouchMs = Date.now();
|
|
134
|
-
this._modality.next('touch');
|
|
135
|
-
this._mostRecentTarget = _getEventTarget(event);
|
|
136
|
-
};
|
|
137
|
-
constructor() {
|
|
138
|
-
const ngZone = inject(NgZone);
|
|
139
|
-
const document = inject(DOCUMENT);
|
|
140
|
-
const options = inject(INPUT_MODALITY_DETECTOR_OPTIONS, { optional: true });
|
|
141
|
-
this._options = {
|
|
142
|
-
...INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS,
|
|
143
|
-
...options,
|
|
144
|
-
};
|
|
145
|
-
// Skip the first emission as it's null.
|
|
146
|
-
this.modalityDetected = this._modality.pipe(skip(1));
|
|
147
|
-
this.modalityChanged = this.modalityDetected.pipe(distinctUntilChanged());
|
|
148
|
-
// If we're not in a browser, this service should do nothing, as there's no relevant input
|
|
149
|
-
// modality to detect.
|
|
150
|
-
if (this._platform.isBrowser) {
|
|
151
|
-
const renderer = inject(RendererFactory2).createRenderer(null, null);
|
|
152
|
-
this._listenerCleanups = ngZone.runOutsideAngular(() => {
|
|
153
|
-
return [
|
|
154
|
-
renderer.listen(document, 'keydown', this._onKeydown, modalityEventListenerOptions),
|
|
155
|
-
renderer.listen(document, 'mousedown', this._onMousedown, modalityEventListenerOptions),
|
|
156
|
-
renderer.listen(document, 'touchstart', this._onTouchstart, modalityEventListenerOptions),
|
|
157
|
-
];
|
|
158
|
-
});
|
|
159
|
-
}
|
|
37
|
+
this._modality.next('keyboard');
|
|
38
|
+
this._mostRecentTarget = _getEventTarget(event);
|
|
39
|
+
};
|
|
40
|
+
_onMousedown = event => {
|
|
41
|
+
if (Date.now() - this._lastTouchMs < TOUCH_BUFFER_MS) {
|
|
42
|
+
return;
|
|
160
43
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
44
|
+
this._modality.next(isFakeMousedownFromScreenReader(event) ? 'keyboard' : 'mouse');
|
|
45
|
+
this._mostRecentTarget = _getEventTarget(event);
|
|
46
|
+
};
|
|
47
|
+
_onTouchstart = event => {
|
|
48
|
+
if (isFakeTouchstartFromScreenReader(event)) {
|
|
49
|
+
this._modality.next('keyboard');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this._lastTouchMs = Date.now();
|
|
53
|
+
this._modality.next('touch');
|
|
54
|
+
this._mostRecentTarget = _getEventTarget(event);
|
|
55
|
+
};
|
|
56
|
+
constructor() {
|
|
57
|
+
const ngZone = inject(NgZone);
|
|
58
|
+
const document = inject(DOCUMENT);
|
|
59
|
+
const options = inject(INPUT_MODALITY_DETECTOR_OPTIONS, {
|
|
60
|
+
optional: true
|
|
61
|
+
});
|
|
62
|
+
this._options = {
|
|
63
|
+
...INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS,
|
|
64
|
+
...options
|
|
65
|
+
};
|
|
66
|
+
this.modalityDetected = this._modality.pipe(skip(1));
|
|
67
|
+
this.modalityChanged = this.modalityDetected.pipe(distinctUntilChanged());
|
|
68
|
+
if (this._platform.isBrowser) {
|
|
69
|
+
const renderer = inject(RendererFactory2).createRenderer(null, null);
|
|
70
|
+
this._listenerCleanups = ngZone.runOutsideAngular(() => {
|
|
71
|
+
return [renderer.listen(document, 'keydown', this._onKeydown, modalityEventListenerOptions), renderer.listen(document, 'mousedown', this._onMousedown, modalityEventListenerOptions), renderer.listen(document, 'touchstart', this._onTouchstart, modalityEventListenerOptions)];
|
|
72
|
+
});
|
|
164
73
|
}
|
|
165
|
-
|
|
166
|
-
|
|
74
|
+
}
|
|
75
|
+
ngOnDestroy() {
|
|
76
|
+
this._modality.complete();
|
|
77
|
+
this._listenerCleanups?.forEach(cleanup => cleanup());
|
|
78
|
+
}
|
|
79
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
80
|
+
minVersion: "12.0.0",
|
|
81
|
+
version: "20.2.0-next.2",
|
|
82
|
+
ngImport: i0,
|
|
83
|
+
type: InputModalityDetector,
|
|
84
|
+
deps: [],
|
|
85
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
86
|
+
});
|
|
87
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
88
|
+
minVersion: "12.0.0",
|
|
89
|
+
version: "20.2.0-next.2",
|
|
90
|
+
ngImport: i0,
|
|
91
|
+
type: InputModalityDetector,
|
|
92
|
+
providedIn: 'root'
|
|
93
|
+
});
|
|
167
94
|
}
|
|
168
|
-
i0.ɵɵngDeclareClassMetadata({
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
95
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
96
|
+
minVersion: "12.0.0",
|
|
97
|
+
version: "20.2.0-next.2",
|
|
98
|
+
ngImport: i0,
|
|
99
|
+
type: InputModalityDetector,
|
|
100
|
+
decorators: [{
|
|
101
|
+
type: Injectable,
|
|
102
|
+
args: [{
|
|
103
|
+
providedIn: 'root'
|
|
104
|
+
}]
|
|
105
|
+
}],
|
|
106
|
+
ctorParameters: () => []
|
|
107
|
+
});
|
|
172
108
|
|
|
173
|
-
/** Detection mode used for attributing the origin of a focus event. */
|
|
174
109
|
var FocusMonitorDetectionMode;
|
|
175
110
|
(function (FocusMonitorDetectionMode) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
* tick or the current tick will be used to assign a focus event's origin (to
|
|
179
|
-
* either mouse, keyboard, or touch). This is the default option.
|
|
180
|
-
*/
|
|
181
|
-
FocusMonitorDetectionMode[FocusMonitorDetectionMode["IMMEDIATE"] = 0] = "IMMEDIATE";
|
|
182
|
-
/**
|
|
183
|
-
* A focus event's origin is always attributed to the last corresponding
|
|
184
|
-
* mousedown, keydown, or touchstart event, no matter how long ago it occurred.
|
|
185
|
-
*/
|
|
186
|
-
FocusMonitorDetectionMode[FocusMonitorDetectionMode["EVENTUAL"] = 1] = "EVENTUAL";
|
|
111
|
+
FocusMonitorDetectionMode[FocusMonitorDetectionMode["IMMEDIATE"] = 0] = "IMMEDIATE";
|
|
112
|
+
FocusMonitorDetectionMode[FocusMonitorDetectionMode["EVENTUAL"] = 1] = "EVENTUAL";
|
|
187
113
|
})(FocusMonitorDetectionMode || (FocusMonitorDetectionMode = {}));
|
|
188
|
-
/** InjectionToken for FocusMonitorOptions. */
|
|
189
114
|
const FOCUS_MONITOR_DEFAULT_OPTIONS = new InjectionToken('cdk-focus-monitor-default-options');
|
|
190
|
-
/**
|
|
191
|
-
* Event listener options that enable capturing and also
|
|
192
|
-
* mark the listener as passive if the browser supports it.
|
|
193
|
-
*/
|
|
194
115
|
const captureEventListenerOptions = normalizePassiveListenerOptions({
|
|
195
|
-
|
|
196
|
-
|
|
116
|
+
passive: true,
|
|
117
|
+
capture: true
|
|
197
118
|
});
|
|
198
|
-
/** Monitors mouse and keyboard events to determine the cause of focus events. */
|
|
199
119
|
class FocusMonitor {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
_detectionMode;
|
|
234
|
-
/**
|
|
235
|
-
* Event listener for `focus` events on the window.
|
|
236
|
-
* Needs to be an arrow function in order to preserve the context when it gets bound.
|
|
237
|
-
*/
|
|
238
|
-
_windowFocusListener = () => {
|
|
239
|
-
// Make a note of when the window regains focus, so we can
|
|
240
|
-
// restore the origin info for the focused element.
|
|
241
|
-
this._windowFocused = true;
|
|
242
|
-
this._windowFocusTimeoutId = setTimeout(() => (this._windowFocused = false));
|
|
243
|
-
};
|
|
244
|
-
/** Used to reference correct document/window */
|
|
245
|
-
_document = inject(DOCUMENT);
|
|
246
|
-
/** Subject for stopping our InputModalityDetector subscription. */
|
|
247
|
-
_stopInputModalityDetector = new Subject();
|
|
248
|
-
constructor() {
|
|
249
|
-
const options = inject(FOCUS_MONITOR_DEFAULT_OPTIONS, {
|
|
250
|
-
optional: true,
|
|
251
|
-
});
|
|
252
|
-
this._detectionMode = options?.detectionMode || FocusMonitorDetectionMode.IMMEDIATE;
|
|
120
|
+
_ngZone = inject(NgZone);
|
|
121
|
+
_platform = inject(Platform);
|
|
122
|
+
_inputModalityDetector = inject(InputModalityDetector);
|
|
123
|
+
_origin = null;
|
|
124
|
+
_lastFocusOrigin;
|
|
125
|
+
_windowFocused = false;
|
|
126
|
+
_windowFocusTimeoutId;
|
|
127
|
+
_originTimeoutId;
|
|
128
|
+
_originFromTouchInteraction = false;
|
|
129
|
+
_elementInfo = new Map();
|
|
130
|
+
_monitoredElementCount = 0;
|
|
131
|
+
_rootNodeFocusListenerCount = new Map();
|
|
132
|
+
_detectionMode;
|
|
133
|
+
_windowFocusListener = () => {
|
|
134
|
+
this._windowFocused = true;
|
|
135
|
+
this._windowFocusTimeoutId = setTimeout(() => this._windowFocused = false);
|
|
136
|
+
};
|
|
137
|
+
_document = inject(DOCUMENT);
|
|
138
|
+
_stopInputModalityDetector = new Subject();
|
|
139
|
+
constructor() {
|
|
140
|
+
const options = inject(FOCUS_MONITOR_DEFAULT_OPTIONS, {
|
|
141
|
+
optional: true
|
|
142
|
+
});
|
|
143
|
+
this._detectionMode = options?.detectionMode || FocusMonitorDetectionMode.IMMEDIATE;
|
|
144
|
+
}
|
|
145
|
+
_rootNodeFocusAndBlurListener = event => {
|
|
146
|
+
const target = _getEventTarget(event);
|
|
147
|
+
for (let element = target; element; element = element.parentElement) {
|
|
148
|
+
if (event.type === 'focus') {
|
|
149
|
+
this._onFocus(event, element);
|
|
150
|
+
} else {
|
|
151
|
+
this._onBlur(event, element);
|
|
152
|
+
}
|
|
253
153
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const target = _getEventTarget(event);
|
|
260
|
-
// We need to walk up the ancestor chain in order to support `checkChildren`.
|
|
261
|
-
for (let element = target; element; element = element.parentElement) {
|
|
262
|
-
if (event.type === 'focus') {
|
|
263
|
-
this._onFocus(event, element);
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
this._onBlur(event, element);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
monitor(element, checkChildren = false) {
|
|
271
|
-
const nativeElement = coerceElement(element);
|
|
272
|
-
// Do nothing if we're not on the browser platform or the passed in node isn't an element.
|
|
273
|
-
if (!this._platform.isBrowser || nativeElement.nodeType !== 1) {
|
|
274
|
-
// Note: we don't want the observable to emit at all so we don't pass any parameters.
|
|
275
|
-
return of();
|
|
276
|
-
}
|
|
277
|
-
// If the element is inside the shadow DOM, we need to bind our focus/blur listeners to
|
|
278
|
-
// the shadow root, rather than the `document`, because the browser won't emit focus events
|
|
279
|
-
// to the `document`, if focus is moving within the same shadow root.
|
|
280
|
-
const rootNode = _getShadowRoot(nativeElement) || this._document;
|
|
281
|
-
const cachedInfo = this._elementInfo.get(nativeElement);
|
|
282
|
-
// Check if we're already monitoring this element.
|
|
283
|
-
if (cachedInfo) {
|
|
284
|
-
if (checkChildren) {
|
|
285
|
-
// TODO(COMP-318): this can be problematic, because it'll turn all non-checkChildren
|
|
286
|
-
// observers into ones that behave as if `checkChildren` was turned on. We need a more
|
|
287
|
-
// robust solution.
|
|
288
|
-
cachedInfo.checkChildren = true;
|
|
289
|
-
}
|
|
290
|
-
return cachedInfo.subject;
|
|
291
|
-
}
|
|
292
|
-
// Create monitored element info.
|
|
293
|
-
const info = {
|
|
294
|
-
checkChildren: checkChildren,
|
|
295
|
-
subject: new Subject(),
|
|
296
|
-
rootNode,
|
|
297
|
-
};
|
|
298
|
-
this._elementInfo.set(nativeElement, info);
|
|
299
|
-
this._registerGlobalListeners(info);
|
|
300
|
-
return info.subject;
|
|
154
|
+
};
|
|
155
|
+
monitor(element, checkChildren = false) {
|
|
156
|
+
const nativeElement = coerceElement(element);
|
|
157
|
+
if (!this._platform.isBrowser || nativeElement.nodeType !== 1) {
|
|
158
|
+
return of();
|
|
301
159
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this._removeGlobalListeners(elementInfo);
|
|
310
|
-
}
|
|
160
|
+
const rootNode = _getShadowRoot(nativeElement) || this._document;
|
|
161
|
+
const cachedInfo = this._elementInfo.get(nativeElement);
|
|
162
|
+
if (cachedInfo) {
|
|
163
|
+
if (checkChildren) {
|
|
164
|
+
cachedInfo.checkChildren = true;
|
|
165
|
+
}
|
|
166
|
+
return cachedInfo.subject;
|
|
311
167
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
168
|
+
const info = {
|
|
169
|
+
checkChildren: checkChildren,
|
|
170
|
+
subject: new Subject(),
|
|
171
|
+
rootNode
|
|
172
|
+
};
|
|
173
|
+
this._elementInfo.set(nativeElement, info);
|
|
174
|
+
this._registerGlobalListeners(info);
|
|
175
|
+
return info.subject;
|
|
176
|
+
}
|
|
177
|
+
stopMonitoring(element) {
|
|
178
|
+
const nativeElement = coerceElement(element);
|
|
179
|
+
const elementInfo = this._elementInfo.get(nativeElement);
|
|
180
|
+
if (elementInfo) {
|
|
181
|
+
elementInfo.subject.complete();
|
|
182
|
+
this._setClasses(nativeElement);
|
|
183
|
+
this._elementInfo.delete(nativeElement);
|
|
184
|
+
this._removeGlobalListeners(elementInfo);
|
|
328
185
|
}
|
|
329
|
-
|
|
330
|
-
|
|
186
|
+
}
|
|
187
|
+
focusVia(element, origin, options) {
|
|
188
|
+
const nativeElement = coerceElement(element);
|
|
189
|
+
const focusedElement = this._document.activeElement;
|
|
190
|
+
if (nativeElement === focusedElement) {
|
|
191
|
+
this._getClosestElementsInfo(nativeElement).forEach(([currentElement, info]) => this._originChanged(currentElement, origin, info));
|
|
192
|
+
} else {
|
|
193
|
+
this._setOrigin(origin);
|
|
194
|
+
if (typeof nativeElement.focus === 'function') {
|
|
195
|
+
nativeElement.focus(options);
|
|
196
|
+
}
|
|
331
197
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
198
|
+
}
|
|
199
|
+
ngOnDestroy() {
|
|
200
|
+
this._elementInfo.forEach((_info, element) => this.stopMonitoring(element));
|
|
201
|
+
}
|
|
202
|
+
_getWindow() {
|
|
203
|
+
return this._document.defaultView || window;
|
|
204
|
+
}
|
|
205
|
+
_getFocusOrigin(focusEventTarget) {
|
|
206
|
+
if (this._origin) {
|
|
207
|
+
if (this._originFromTouchInteraction) {
|
|
208
|
+
return this._shouldBeAttributedToTouch(focusEventTarget) ? 'touch' : 'program';
|
|
209
|
+
} else {
|
|
210
|
+
return this._origin;
|
|
211
|
+
}
|
|
335
212
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// If the origin was realized via a touch interaction, we need to perform additional checks
|
|
339
|
-
// to determine whether the focus origin should be attributed to touch or program.
|
|
340
|
-
if (this._originFromTouchInteraction) {
|
|
341
|
-
return this._shouldBeAttributedToTouch(focusEventTarget) ? 'touch' : 'program';
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
return this._origin;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
// If the window has just regained focus, we can restore the most recent origin from before the
|
|
348
|
-
// window blurred. Otherwise, we've reached the point where we can't identify the source of the
|
|
349
|
-
// focus. This typically means one of two things happened:
|
|
350
|
-
//
|
|
351
|
-
// 1) The element was programmatically focused, or
|
|
352
|
-
// 2) The element was focused via screen reader navigation (which generally doesn't fire
|
|
353
|
-
// events).
|
|
354
|
-
//
|
|
355
|
-
// Because we can't distinguish between these two cases, we default to setting `program`.
|
|
356
|
-
if (this._windowFocused && this._lastFocusOrigin) {
|
|
357
|
-
return this._lastFocusOrigin;
|
|
358
|
-
}
|
|
359
|
-
// If the interaction is coming from an input label, we consider it a mouse interactions.
|
|
360
|
-
// This is a special case where focus moves on `click`, rather than `mousedown` which breaks
|
|
361
|
-
// our detection, because all our assumptions are for `mousedown`. We need to handle this
|
|
362
|
-
// special case, because it's very common for checkboxes and radio buttons.
|
|
363
|
-
if (focusEventTarget && this._isLastInteractionFromInputLabel(focusEventTarget)) {
|
|
364
|
-
return 'mouse';
|
|
365
|
-
}
|
|
366
|
-
return 'program';
|
|
213
|
+
if (this._windowFocused && this._lastFocusOrigin) {
|
|
214
|
+
return this._lastFocusOrigin;
|
|
367
215
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
* touch origin isn't immediately reset at the next tick (see _setOrigin). This means that when we
|
|
371
|
-
* handle a focus event following a touch interaction, we need to determine whether (1) the focus
|
|
372
|
-
* event was directly caused by the touch interaction or (2) the focus event was caused by a
|
|
373
|
-
* subsequent programmatic focus call triggered by the touch interaction.
|
|
374
|
-
* @param focusEventTarget The target of the focus event under examination.
|
|
375
|
-
*/
|
|
376
|
-
_shouldBeAttributedToTouch(focusEventTarget) {
|
|
377
|
-
// Please note that this check is not perfect. Consider the following edge case:
|
|
378
|
-
//
|
|
379
|
-
// <div #parent tabindex="0">
|
|
380
|
-
// <div #child tabindex="0" (click)="#parent.focus()"></div>
|
|
381
|
-
// </div>
|
|
382
|
-
//
|
|
383
|
-
// Suppose there is a FocusMonitor in IMMEDIATE mode attached to #parent. When the user touches
|
|
384
|
-
// #child, #parent is programmatically focused. This code will attribute the focus to touch
|
|
385
|
-
// instead of program. This is a relatively minor edge-case that can be worked around by using
|
|
386
|
-
// focusVia(parent, 'program') to focus #parent.
|
|
387
|
-
return (this._detectionMode === FocusMonitorDetectionMode.EVENTUAL ||
|
|
388
|
-
!!focusEventTarget?.contains(this._inputModalityDetector._mostRecentTarget));
|
|
216
|
+
if (focusEventTarget && this._isLastInteractionFromInputLabel(focusEventTarget)) {
|
|
217
|
+
return 'mouse';
|
|
389
218
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
219
|
+
return 'program';
|
|
220
|
+
}
|
|
221
|
+
_shouldBeAttributedToTouch(focusEventTarget) {
|
|
222
|
+
return this._detectionMode === FocusMonitorDetectionMode.EVENTUAL || !!focusEventTarget?.contains(this._inputModalityDetector._mostRecentTarget);
|
|
223
|
+
}
|
|
224
|
+
_setClasses(element, origin) {
|
|
225
|
+
element.classList.toggle('cdk-focused', !!origin);
|
|
226
|
+
element.classList.toggle('cdk-touch-focused', origin === 'touch');
|
|
227
|
+
element.classList.toggle('cdk-keyboard-focused', origin === 'keyboard');
|
|
228
|
+
element.classList.toggle('cdk-mouse-focused', origin === 'mouse');
|
|
229
|
+
element.classList.toggle('cdk-program-focused', origin === 'program');
|
|
230
|
+
}
|
|
231
|
+
_setOrigin(origin, isFromInteraction = false) {
|
|
232
|
+
this._ngZone.runOutsideAngular(() => {
|
|
233
|
+
this._origin = origin;
|
|
234
|
+
this._originFromTouchInteraction = origin === 'touch' && isFromInteraction;
|
|
235
|
+
if (this._detectionMode === FocusMonitorDetectionMode.IMMEDIATE) {
|
|
236
|
+
clearTimeout(this._originTimeoutId);
|
|
237
|
+
const ms = this._originFromTouchInteraction ? TOUCH_BUFFER_MS : 1;
|
|
238
|
+
this._originTimeoutId = setTimeout(() => this._origin = null, ms);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
_onFocus(event, element) {
|
|
243
|
+
const elementInfo = this._elementInfo.get(element);
|
|
244
|
+
const focusEventTarget = _getEventTarget(event);
|
|
245
|
+
if (!elementInfo || !elementInfo.checkChildren && element !== focusEventTarget) {
|
|
246
|
+
return;
|
|
401
247
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
*/
|
|
409
|
-
_setOrigin(origin, isFromInteraction = false) {
|
|
410
|
-
this._ngZone.runOutsideAngular(() => {
|
|
411
|
-
this._origin = origin;
|
|
412
|
-
this._originFromTouchInteraction = origin === 'touch' && isFromInteraction;
|
|
413
|
-
// If we're in IMMEDIATE mode, reset the origin at the next tick (or in `TOUCH_BUFFER_MS` ms
|
|
414
|
-
// for a touch event). We reset the origin at the next tick because Firefox focuses one tick
|
|
415
|
-
// after the interaction event. We wait `TOUCH_BUFFER_MS` ms before resetting the origin for
|
|
416
|
-
// a touch event because when a touch event is fired, the associated focus event isn't yet in
|
|
417
|
-
// the event queue. Before doing so, clear any pending timeouts.
|
|
418
|
-
if (this._detectionMode === FocusMonitorDetectionMode.IMMEDIATE) {
|
|
419
|
-
clearTimeout(this._originTimeoutId);
|
|
420
|
-
const ms = this._originFromTouchInteraction ? TOUCH_BUFFER_MS : 1;
|
|
421
|
-
this._originTimeoutId = setTimeout(() => (this._origin = null), ms);
|
|
422
|
-
}
|
|
423
|
-
});
|
|
248
|
+
this._originChanged(element, this._getFocusOrigin(focusEventTarget), elementInfo);
|
|
249
|
+
}
|
|
250
|
+
_onBlur(event, element) {
|
|
251
|
+
const elementInfo = this._elementInfo.get(element);
|
|
252
|
+
if (!elementInfo || elementInfo.checkChildren && event.relatedTarget instanceof Node && element.contains(event.relatedTarget)) {
|
|
253
|
+
return;
|
|
424
254
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
// NOTE(mmalerba): We currently set the classes based on the focus origin of the most recent
|
|
432
|
-
// focus event affecting the monitored element. If we want to use the origin of the first event
|
|
433
|
-
// instead we should check for the cdk-focused class here and return if the element already has
|
|
434
|
-
// it. (This only matters for elements that have includesChildren = true).
|
|
435
|
-
// If we are not counting child-element-focus as focused, make sure that the event target is the
|
|
436
|
-
// monitored element itself.
|
|
437
|
-
const elementInfo = this._elementInfo.get(element);
|
|
438
|
-
const focusEventTarget = _getEventTarget(event);
|
|
439
|
-
if (!elementInfo || (!elementInfo.checkChildren && element !== focusEventTarget)) {
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
this._originChanged(element, this._getFocusOrigin(focusEventTarget), elementInfo);
|
|
255
|
+
this._setClasses(element);
|
|
256
|
+
this._emitOrigin(elementInfo, null);
|
|
257
|
+
}
|
|
258
|
+
_emitOrigin(info, origin) {
|
|
259
|
+
if (info.subject.observers.length) {
|
|
260
|
+
this._ngZone.run(() => info.subject.next(origin));
|
|
443
261
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
*/
|
|
449
|
-
_onBlur(event, element) {
|
|
450
|
-
// If we are counting child-element-focus as focused, make sure that we aren't just blurring in
|
|
451
|
-
// order to focus another child of the monitored element.
|
|
452
|
-
const elementInfo = this._elementInfo.get(element);
|
|
453
|
-
if (!elementInfo ||
|
|
454
|
-
(elementInfo.checkChildren &&
|
|
455
|
-
event.relatedTarget instanceof Node &&
|
|
456
|
-
element.contains(event.relatedTarget))) {
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
this._setClasses(element);
|
|
460
|
-
this._emitOrigin(elementInfo, null);
|
|
262
|
+
}
|
|
263
|
+
_registerGlobalListeners(elementInfo) {
|
|
264
|
+
if (!this._platform.isBrowser) {
|
|
265
|
+
return;
|
|
461
266
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
267
|
+
const rootNode = elementInfo.rootNode;
|
|
268
|
+
const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode) || 0;
|
|
269
|
+
if (!rootNodeFocusListeners) {
|
|
270
|
+
this._ngZone.runOutsideAngular(() => {
|
|
271
|
+
rootNode.addEventListener('focus', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
272
|
+
rootNode.addEventListener('blur', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
273
|
+
});
|
|
466
274
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
rootNode.addEventListener('blur', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners + 1);
|
|
480
|
-
// Register global listeners when first element is monitored.
|
|
481
|
-
if (++this._monitoredElementCount === 1) {
|
|
482
|
-
// Note: we listen to events in the capture phase so we
|
|
483
|
-
// can detect them even if the user stops propagation.
|
|
484
|
-
this._ngZone.runOutsideAngular(() => {
|
|
485
|
-
const window = this._getWindow();
|
|
486
|
-
window.addEventListener('focus', this._windowFocusListener);
|
|
487
|
-
});
|
|
488
|
-
// The InputModalityDetector is also just a collection of global listeners.
|
|
489
|
-
this._inputModalityDetector.modalityDetected
|
|
490
|
-
.pipe(takeUntil(this._stopInputModalityDetector))
|
|
491
|
-
.subscribe(modality => {
|
|
492
|
-
this._setOrigin(modality, true /* isFromInteraction */);
|
|
493
|
-
});
|
|
494
|
-
}
|
|
275
|
+
this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners + 1);
|
|
276
|
+
if (++this._monitoredElementCount === 1) {
|
|
277
|
+
this._ngZone.runOutsideAngular(() => {
|
|
278
|
+
const window = this._getWindow();
|
|
279
|
+
window.addEventListener('focus', this._windowFocusListener);
|
|
280
|
+
});
|
|
281
|
+
this._inputModalityDetector.modalityDetected.pipe(takeUntil(this._stopInputModalityDetector)).subscribe(modality => {
|
|
282
|
+
this._setOrigin(modality, true);
|
|
283
|
+
});
|
|
495
284
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
// Unregister global listeners when last element is unmonitored.
|
|
510
|
-
if (!--this._monitoredElementCount) {
|
|
511
|
-
const window = this._getWindow();
|
|
512
|
-
window.removeEventListener('focus', this._windowFocusListener);
|
|
513
|
-
// Equivalently, stop our InputModalityDetector subscription.
|
|
514
|
-
this._stopInputModalityDetector.next();
|
|
515
|
-
// Clear timeouts for all potentially pending timeouts to prevent the leaks.
|
|
516
|
-
clearTimeout(this._windowFocusTimeoutId);
|
|
517
|
-
clearTimeout(this._originTimeoutId);
|
|
518
|
-
}
|
|
285
|
+
}
|
|
286
|
+
_removeGlobalListeners(elementInfo) {
|
|
287
|
+
const rootNode = elementInfo.rootNode;
|
|
288
|
+
if (this._rootNodeFocusListenerCount.has(rootNode)) {
|
|
289
|
+
const rootNodeFocusListeners = this._rootNodeFocusListenerCount.get(rootNode);
|
|
290
|
+
if (rootNodeFocusListeners > 1) {
|
|
291
|
+
this._rootNodeFocusListenerCount.set(rootNode, rootNodeFocusListeners - 1);
|
|
292
|
+
} else {
|
|
293
|
+
rootNode.removeEventListener('focus', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
294
|
+
rootNode.removeEventListener('blur', this._rootNodeFocusAndBlurListener, captureEventListenerOptions);
|
|
295
|
+
this._rootNodeFocusListenerCount.delete(rootNode);
|
|
296
|
+
}
|
|
519
297
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
298
|
+
if (! --this._monitoredElementCount) {
|
|
299
|
+
const window = this._getWindow();
|
|
300
|
+
window.removeEventListener('focus', this._windowFocusListener);
|
|
301
|
+
this._stopInputModalityDetector.next();
|
|
302
|
+
clearTimeout(this._windowFocusTimeoutId);
|
|
303
|
+
clearTimeout(this._originTimeoutId);
|
|
525
304
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
305
|
+
}
|
|
306
|
+
_originChanged(element, origin, elementInfo) {
|
|
307
|
+
this._setClasses(element, origin);
|
|
308
|
+
this._emitOrigin(elementInfo, origin);
|
|
309
|
+
this._lastFocusOrigin = origin;
|
|
310
|
+
}
|
|
311
|
+
_getClosestElementsInfo(element) {
|
|
312
|
+
const results = [];
|
|
313
|
+
this._elementInfo.forEach((info, currentElement) => {
|
|
314
|
+
if (currentElement === element || info.checkChildren && currentElement.contains(element)) {
|
|
315
|
+
results.push([currentElement, info]);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
return results;
|
|
319
|
+
}
|
|
320
|
+
_isLastInteractionFromInputLabel(focusEventTarget) {
|
|
321
|
+
const {
|
|
322
|
+
_mostRecentTarget: mostRecentTarget,
|
|
323
|
+
mostRecentModality
|
|
324
|
+
} = this._inputModalityDetector;
|
|
325
|
+
if (mostRecentModality !== 'mouse' || !mostRecentTarget || mostRecentTarget === focusEventTarget || focusEventTarget.nodeName !== 'INPUT' && focusEventTarget.nodeName !== 'TEXTAREA' || focusEventTarget.disabled) {
|
|
326
|
+
return false;
|
|
539
327
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
_isLastInteractionFromInputLabel(focusEventTarget) {
|
|
546
|
-
const { _mostRecentTarget: mostRecentTarget, mostRecentModality } = this._inputModalityDetector;
|
|
547
|
-
// If the last interaction used the mouse on an element contained by one of the labels
|
|
548
|
-
// of an `input`/`textarea` that is currently focused, it is very likely that the
|
|
549
|
-
// user redirected focus using the label.
|
|
550
|
-
if (mostRecentModality !== 'mouse' ||
|
|
551
|
-
!mostRecentTarget ||
|
|
552
|
-
mostRecentTarget === focusEventTarget ||
|
|
553
|
-
(focusEventTarget.nodeName !== 'INPUT' && focusEventTarget.nodeName !== 'TEXTAREA') ||
|
|
554
|
-
focusEventTarget.disabled) {
|
|
555
|
-
return false;
|
|
328
|
+
const labels = focusEventTarget.labels;
|
|
329
|
+
if (labels) {
|
|
330
|
+
for (let i = 0; i < labels.length; i++) {
|
|
331
|
+
if (labels[i].contains(mostRecentTarget)) {
|
|
332
|
+
return true;
|
|
556
333
|
}
|
|
557
|
-
|
|
558
|
-
if (labels) {
|
|
559
|
-
for (let i = 0; i < labels.length; i++) {
|
|
560
|
-
if (labels[i].contains(mostRecentTarget)) {
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
return false;
|
|
334
|
+
}
|
|
566
335
|
}
|
|
567
|
-
|
|
568
|
-
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
339
|
+
minVersion: "12.0.0",
|
|
340
|
+
version: "20.2.0-next.2",
|
|
341
|
+
ngImport: i0,
|
|
342
|
+
type: FocusMonitor,
|
|
343
|
+
deps: [],
|
|
344
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
345
|
+
});
|
|
346
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
347
|
+
minVersion: "12.0.0",
|
|
348
|
+
version: "20.2.0-next.2",
|
|
349
|
+
ngImport: i0,
|
|
350
|
+
type: FocusMonitor,
|
|
351
|
+
providedIn: 'root'
|
|
352
|
+
});
|
|
569
353
|
}
|
|
570
|
-
i0.ɵɵngDeclareClassMetadata({
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
354
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
355
|
+
minVersion: "12.0.0",
|
|
356
|
+
version: "20.2.0-next.2",
|
|
357
|
+
ngImport: i0,
|
|
358
|
+
type: FocusMonitor,
|
|
359
|
+
decorators: [{
|
|
360
|
+
type: Injectable,
|
|
361
|
+
args: [{
|
|
362
|
+
providedIn: 'root'
|
|
363
|
+
}]
|
|
364
|
+
}],
|
|
365
|
+
ctorParameters: () => []
|
|
366
|
+
});
|
|
583
367
|
class CdkMonitorFocus {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
368
|
+
_elementRef = inject(ElementRef);
|
|
369
|
+
_focusMonitor = inject(FocusMonitor);
|
|
370
|
+
_monitorSubscription;
|
|
371
|
+
_focusOrigin = null;
|
|
372
|
+
cdkFocusChange = new EventEmitter();
|
|
373
|
+
constructor() {}
|
|
374
|
+
get focusOrigin() {
|
|
375
|
+
return this._focusOrigin;
|
|
376
|
+
}
|
|
377
|
+
ngAfterViewInit() {
|
|
378
|
+
const element = this._elementRef.nativeElement;
|
|
379
|
+
this._monitorSubscription = this._focusMonitor.monitor(element, element.nodeType === 1 && element.hasAttribute('cdkMonitorSubtreeFocus')).subscribe(origin => {
|
|
380
|
+
this._focusOrigin = origin;
|
|
381
|
+
this.cdkFocusChange.emit(origin);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
ngOnDestroy() {
|
|
385
|
+
this._focusMonitor.stopMonitoring(this._elementRef);
|
|
386
|
+
if (this._monitorSubscription) {
|
|
387
|
+
this._monitorSubscription.unsubscribe();
|
|
592
388
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
389
|
+
}
|
|
390
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
391
|
+
minVersion: "12.0.0",
|
|
392
|
+
version: "20.2.0-next.2",
|
|
393
|
+
ngImport: i0,
|
|
394
|
+
type: CdkMonitorFocus,
|
|
395
|
+
deps: [],
|
|
396
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
397
|
+
});
|
|
398
|
+
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
399
|
+
minVersion: "14.0.0",
|
|
400
|
+
version: "20.2.0-next.2",
|
|
401
|
+
type: CdkMonitorFocus,
|
|
402
|
+
isStandalone: true,
|
|
403
|
+
selector: "[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]",
|
|
404
|
+
outputs: {
|
|
405
|
+
cdkFocusChange: "cdkFocusChange"
|
|
406
|
+
},
|
|
407
|
+
exportAs: ["cdkMonitorFocus"],
|
|
408
|
+
ngImport: i0
|
|
409
|
+
});
|
|
610
410
|
}
|
|
611
|
-
i0.ɵɵngDeclareClassMetadata({
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
411
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
412
|
+
minVersion: "12.0.0",
|
|
413
|
+
version: "20.2.0-next.2",
|
|
414
|
+
ngImport: i0,
|
|
415
|
+
type: CdkMonitorFocus,
|
|
416
|
+
decorators: [{
|
|
417
|
+
type: Directive,
|
|
418
|
+
args: [{
|
|
419
|
+
selector: '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]',
|
|
420
|
+
exportAs: 'cdkMonitorFocus'
|
|
421
|
+
}]
|
|
422
|
+
}],
|
|
423
|
+
ctorParameters: () => [],
|
|
424
|
+
propDecorators: {
|
|
425
|
+
cdkFocusChange: [{
|
|
426
|
+
type: Output
|
|
427
|
+
}]
|
|
428
|
+
}
|
|
429
|
+
});
|
|
620
430
|
|
|
621
431
|
export { CdkMonitorFocus, FOCUS_MONITOR_DEFAULT_OPTIONS, FocusMonitor, FocusMonitorDetectionMode, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS, INPUT_MODALITY_DETECTOR_OPTIONS, InputModalityDetector };
|
|
622
432
|
//# sourceMappingURL=_focus-monitor-chunk.mjs.map
|