@angular/core 18.0.0-rc.3 → 18.0.1
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/esm2022/primitives/event-dispatch/index.mjs +3 -2
- package/esm2022/primitives/event-dispatch/src/action_resolver.mjs +51 -35
- package/esm2022/primitives/event-dispatch/src/dispatcher.mjs +5 -4
- package/esm2022/primitives/event-dispatch/src/event.mjs +3 -7
- package/esm2022/primitives/event-dispatch/src/event_dispatcher.mjs +130 -0
- package/esm2022/primitives/event-dispatch/src/event_type.mjs +85 -1
- package/esm2022/primitives/event-dispatch/src/eventcontract.mjs +5 -8
- package/esm2022/src/application/create_application.mjs +8 -3
- package/esm2022/src/change_detection/scheduling/ng_zone_scheduling.mjs +4 -11
- package/esm2022/src/change_detection/scheduling/zoneless_scheduling_impl.mjs +2 -2
- package/esm2022/src/core_private_export.mjs +2 -1
- package/esm2022/src/error_details_base_url.mjs +2 -2
- package/esm2022/src/errors.mjs +1 -1
- package/esm2022/src/hydration/annotate.mjs +11 -6
- package/esm2022/src/hydration/event_replay.mjs +23 -26
- package/esm2022/src/image_performance_warning.mjs +3 -3
- package/esm2022/src/platform/platform_ref.mjs +10 -3
- package/esm2022/src/render3/component_ref.mjs +1 -1
- package/esm2022/src/render3/instructions/listener.mjs +4 -7
- package/esm2022/src/version.mjs +1 -1
- package/esm2022/testing/src/logger.mjs +3 -3
- package/esm2022/testing/src/test_bed_compiler.mjs +7 -3
- package/fesm2022/core.mjs +230 -229
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +1402 -1188
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives/signals.mjs +1 -1
- package/fesm2022/rxjs-interop.mjs +1 -1
- package/fesm2022/testing.mjs +7 -3
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +39 -3
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +45 -117
- package/primitives/signals/index.d.ts +1 -1
- package/rxjs-interop/index.d.ts +1 -1
- package/schematics/migrations/invalid-two-way-bindings/bundle.js +4 -2
- package/schematics/migrations/invalid-two-way-bindings/bundle.js.map +2 -2
- package/schematics/ng-generate/control-flow-migration/bundle.js +5 -3
- package/schematics/ng-generate/control-flow-migration/bundle.js.map +2 -2
- package/schematics/ng-generate/standalone-migration/bundle.js +15 -13
- package/schematics/ng-generate/standalone-migration/bundle.js.map +2 -2
- package/testing/index.d.ts +1 -1
- package/esm2022/src/change_detection/scheduling/flags.mjs +0 -10
|
@@ -1,231 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v18.0.
|
|
2
|
+
* @license Angular v18.0.1
|
|
3
3
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/** Added for readability when accessing stable property names. */
|
|
8
|
-
function getEventType(eventInfo) {
|
|
9
|
-
return eventInfo.eventType;
|
|
10
|
-
}
|
|
11
|
-
/** Added for readability when accessing stable property names. */
|
|
12
|
-
function setEventType(eventInfo, eventType) {
|
|
13
|
-
eventInfo.eventType = eventType;
|
|
14
|
-
}
|
|
15
|
-
/** Added for readability when accessing stable property names. */
|
|
16
|
-
function getEvent(eventInfo) {
|
|
17
|
-
return eventInfo.event;
|
|
18
|
-
}
|
|
19
|
-
/** Added for readability when accessing stable property names. */
|
|
20
|
-
function setEvent(eventInfo, event) {
|
|
21
|
-
eventInfo.event = event;
|
|
22
|
-
}
|
|
23
|
-
/** Added for readability when accessing stable property names. */
|
|
24
|
-
function getTargetElement(eventInfo) {
|
|
25
|
-
return eventInfo.targetElement;
|
|
26
|
-
}
|
|
27
|
-
/** Added for readability when accessing stable property names. */
|
|
28
|
-
function setTargetElement(eventInfo, targetElement) {
|
|
29
|
-
eventInfo.targetElement = targetElement;
|
|
30
|
-
}
|
|
31
|
-
/** Added for readability when accessing stable property names. */
|
|
32
|
-
function getContainer(eventInfo) {
|
|
33
|
-
return eventInfo.eic;
|
|
34
|
-
}
|
|
35
|
-
/** Added for readability when accessing stable property names. */
|
|
36
|
-
function setContainer(eventInfo, container) {
|
|
37
|
-
eventInfo.eic = container;
|
|
38
|
-
}
|
|
39
|
-
/** Added for readability when accessing stable property names. */
|
|
40
|
-
function getTimestamp(eventInfo) {
|
|
41
|
-
return eventInfo.timeStamp;
|
|
42
|
-
}
|
|
43
|
-
/** Added for readability when accessing stable property names. */
|
|
44
|
-
function setTimestamp(eventInfo, timestamp) {
|
|
45
|
-
eventInfo.timeStamp = timestamp;
|
|
46
|
-
}
|
|
47
|
-
/** Added for readability when accessing stable property names. */
|
|
48
|
-
function getAction(eventInfo) {
|
|
49
|
-
return eventInfo.eia;
|
|
50
|
-
}
|
|
51
|
-
/** Added for readability when accessing stable property names. */
|
|
52
|
-
function setAction(eventInfo, actionName, actionElement) {
|
|
53
|
-
eventInfo.eia = [actionName, actionElement];
|
|
54
|
-
}
|
|
55
|
-
/** Added for readability when accessing stable property names. */
|
|
56
|
-
function unsetAction(eventInfo) {
|
|
57
|
-
eventInfo.eia = undefined;
|
|
58
|
-
}
|
|
59
|
-
/** Added for readability when accessing stable property names. */
|
|
60
|
-
function getActionName(actionInfo) {
|
|
61
|
-
return actionInfo[0];
|
|
62
|
-
}
|
|
63
|
-
/** Added for readability when accessing stable property names. */
|
|
64
|
-
function getActionElement(actionInfo) {
|
|
65
|
-
return actionInfo[1];
|
|
66
|
-
}
|
|
67
|
-
/** Added for readability when accessing stable property names. */
|
|
68
|
-
function getIsReplay(eventInfo) {
|
|
69
|
-
return eventInfo.eirp;
|
|
70
|
-
}
|
|
71
|
-
/** Added for readability when accessing stable property names. */
|
|
72
|
-
function setIsReplay(eventInfo, replay) {
|
|
73
|
-
eventInfo.eirp = replay;
|
|
74
|
-
}
|
|
75
|
-
/** Added for readability when accessing stable property names. */
|
|
76
|
-
function getA11yClickKey(eventInfo) {
|
|
77
|
-
return eventInfo.eiack;
|
|
78
|
-
}
|
|
79
|
-
/** Added for readability when accessing stable property names. */
|
|
80
|
-
function setA11yClickKey(eventInfo, a11yClickKey) {
|
|
81
|
-
eventInfo.eiack = a11yClickKey;
|
|
82
|
-
}
|
|
83
|
-
/** Added for readability when accessing stable property names. */
|
|
84
|
-
function getResolved(eventInfo) {
|
|
85
|
-
return eventInfo.eir;
|
|
86
|
-
}
|
|
87
|
-
/** Added for readability when accessing stable property names. */
|
|
88
|
-
function setResolved(eventInfo, resolved) {
|
|
89
|
-
eventInfo.eir = resolved;
|
|
90
|
-
}
|
|
91
|
-
/** Clones an `EventInfo` */
|
|
92
|
-
function cloneEventInfo(eventInfo) {
|
|
93
|
-
return {
|
|
94
|
-
eventType: eventInfo.eventType,
|
|
95
|
-
event: eventInfo.event,
|
|
96
|
-
targetElement: eventInfo.targetElement,
|
|
97
|
-
eic: eventInfo.eic,
|
|
98
|
-
eia: eventInfo.eia,
|
|
99
|
-
timeStamp: eventInfo.timeStamp,
|
|
100
|
-
eirp: eventInfo.eirp,
|
|
101
|
-
eiack: eventInfo.eiack,
|
|
102
|
-
eir: eventInfo.eir,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
7
|
/**
|
|
106
|
-
*
|
|
8
|
+
* The jsaction attribute defines a mapping of a DOM event to a
|
|
9
|
+
* generic event (aka jsaction), to which the actual event handlers
|
|
10
|
+
* that implement the behavior of the application are bound. The
|
|
11
|
+
* value is a semicolon separated list of colon separated pairs of
|
|
12
|
+
* an optional DOM event name and a jsaction name. If the optional
|
|
13
|
+
* DOM event name is omitted, 'click' is assumed. The jsaction names
|
|
14
|
+
* are dot separated pairs of a namespace and a simple jsaction
|
|
15
|
+
* name.
|
|
107
16
|
*
|
|
108
|
-
*
|
|
109
|
-
* parameters vs. an `Object` literal reduces code size.
|
|
17
|
+
* See grammar in README.md for expected syntax in the attribute value.
|
|
110
18
|
*/
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
eventType,
|
|
114
|
-
event,
|
|
115
|
-
targetElement,
|
|
116
|
-
eic: container,
|
|
117
|
-
timeStamp: timestamp,
|
|
118
|
-
eia: action,
|
|
119
|
-
eirp: isReplay,
|
|
120
|
-
eiack: a11yClickKey,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
19
|
+
const JSACTION$1 = 'jsaction';
|
|
123
20
|
/**
|
|
124
|
-
*
|
|
21
|
+
* The oi attribute is a log impression tag for impression logging
|
|
22
|
+
* and action tracking. For an element that carries a jsaction
|
|
23
|
+
* attribute, the element is identified for the purpose of
|
|
24
|
+
* impression logging and click tracking by the dot separated path
|
|
25
|
+
* of all oi attributes in the chain of ancestors of the element.
|
|
125
26
|
*
|
|
126
|
-
*
|
|
127
|
-
* size.
|
|
27
|
+
* Used by ActionFlow.
|
|
128
28
|
*/
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
eventType,
|
|
132
|
-
event,
|
|
133
|
-
targetElement,
|
|
134
|
-
eic: container,
|
|
135
|
-
timeStamp: timestamp,
|
|
136
|
-
eia: action ? [action.name, action.element] : undefined,
|
|
137
|
-
eirp: isReplay,
|
|
138
|
-
eiack: a11yClickKey,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
29
|
+
const OI$1 = 'oi';
|
|
141
30
|
/**
|
|
142
|
-
*
|
|
31
|
+
* The ved attribute is an encoded ClickTrackingCGI proto to track
|
|
32
|
+
* visual elements.
|
|
143
33
|
*
|
|
144
|
-
*
|
|
145
|
-
* size.
|
|
34
|
+
* Used by ActionFlow.
|
|
146
35
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
getEventType() {
|
|
152
|
-
return getEventType(this.eventInfo);
|
|
153
|
-
}
|
|
154
|
-
setEventType(eventType) {
|
|
155
|
-
setEventType(this.eventInfo, eventType);
|
|
156
|
-
}
|
|
157
|
-
getEvent() {
|
|
158
|
-
return getEvent(this.eventInfo);
|
|
159
|
-
}
|
|
160
|
-
setEvent(event) {
|
|
161
|
-
setEvent(this.eventInfo, event);
|
|
162
|
-
}
|
|
163
|
-
getTargetElement() {
|
|
164
|
-
return getTargetElement(this.eventInfo);
|
|
165
|
-
}
|
|
166
|
-
setTargetElement(targetElement) {
|
|
167
|
-
setTargetElement(this.eventInfo, targetElement);
|
|
168
|
-
}
|
|
169
|
-
getContainer() {
|
|
170
|
-
return getContainer(this.eventInfo);
|
|
171
|
-
}
|
|
172
|
-
setContainer(container) {
|
|
173
|
-
setContainer(this.eventInfo, container);
|
|
174
|
-
}
|
|
175
|
-
getTimestamp() {
|
|
176
|
-
return getTimestamp(this.eventInfo);
|
|
177
|
-
}
|
|
178
|
-
setTimestamp(timestamp) {
|
|
179
|
-
setTimestamp(this.eventInfo, timestamp);
|
|
180
|
-
}
|
|
181
|
-
getAction() {
|
|
182
|
-
const action = getAction(this.eventInfo);
|
|
183
|
-
if (!action)
|
|
184
|
-
return undefined;
|
|
185
|
-
return {
|
|
186
|
-
name: action[0],
|
|
187
|
-
element: action[1],
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
setAction(action) {
|
|
191
|
-
if (!action) {
|
|
192
|
-
unsetAction(this.eventInfo);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
setAction(this.eventInfo, action.name, action.element);
|
|
196
|
-
}
|
|
197
|
-
getIsReplay() {
|
|
198
|
-
return getIsReplay(this.eventInfo);
|
|
199
|
-
}
|
|
200
|
-
setIsReplay(replay) {
|
|
201
|
-
setIsReplay(this.eventInfo, replay);
|
|
202
|
-
}
|
|
203
|
-
getResolved() {
|
|
204
|
-
return getResolved(this.eventInfo);
|
|
205
|
-
}
|
|
206
|
-
setResolved(resolved) {
|
|
207
|
-
setResolved(this.eventInfo, resolved);
|
|
208
|
-
}
|
|
209
|
-
clone() {
|
|
210
|
-
return new EventInfoWrapper(cloneEventInfo(this.eventInfo));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/*
|
|
215
|
-
* Names of events that are special to jsaction. These are not all
|
|
216
|
-
* event types that are legal to use in either HTML or the addEvent()
|
|
217
|
-
* API, but these are the ones that are treated specially. All other
|
|
218
|
-
* DOM events can be used in either addEvent() or in the value of the
|
|
219
|
-
* jsaction attribute. Beware of browser specific events or events
|
|
220
|
-
* that don't bubble though: If they are not mentioned here, then
|
|
221
|
-
* event contract doesn't work around their peculiarities.
|
|
36
|
+
const VED = 'ved';
|
|
37
|
+
/**
|
|
38
|
+
* The vet attribute is the visual element type used to identify tracked
|
|
39
|
+
* visual elements.
|
|
222
40
|
*/
|
|
223
|
-
const
|
|
41
|
+
const VET = 'vet';
|
|
42
|
+
/**
|
|
43
|
+
* Support for iteration on reprocessing.
|
|
44
|
+
*
|
|
45
|
+
* Used by ActionFlow.
|
|
46
|
+
*/
|
|
47
|
+
const JSINSTANCE = 'jsinstance';
|
|
48
|
+
/**
|
|
49
|
+
* All click jsactions that happen on the element that carries this
|
|
50
|
+
* attribute or its descendants are automatically logged.
|
|
51
|
+
* Impressions of jsactions on these elements are tracked too, if
|
|
52
|
+
* requested by the impression() method of ActionFlow.
|
|
53
|
+
*
|
|
54
|
+
* Used by ActionFlow.
|
|
55
|
+
*/
|
|
56
|
+
const JSTRACK = 'jstrack';
|
|
57
|
+
const Attribute = { JSACTION: JSACTION$1, OI: OI$1, VED, VET, JSINSTANCE, JSTRACK };
|
|
58
|
+
|
|
59
|
+
const Char = {
|
|
224
60
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
61
|
+
* The separator between the namespace and the action name in the
|
|
62
|
+
* jsaction attribute value.
|
|
227
63
|
*/
|
|
228
|
-
|
|
64
|
+
NAMESPACE_ACTION_SEPARATOR: '.',
|
|
65
|
+
/**
|
|
66
|
+
* The separator between the event name and action in the jsaction
|
|
67
|
+
* attribute value.
|
|
68
|
+
*/
|
|
69
|
+
EVENT_ACTION_SEPARATOR: ':',
|
|
70
|
+
/**
|
|
71
|
+
* The separator between the logged oi attribute values in the &oi=
|
|
72
|
+
* URL parameter value.
|
|
73
|
+
*/
|
|
74
|
+
OI_SEPARATOR: '.',
|
|
75
|
+
/**
|
|
76
|
+
* The separator between the key and the value pairs in the &cad=
|
|
77
|
+
* URL parameter value.
|
|
78
|
+
*/
|
|
79
|
+
CAD_KEY_VALUE_SEPARATOR: ':',
|
|
80
|
+
/**
|
|
81
|
+
* The separator between the key-value pairs in the &cad= URL
|
|
82
|
+
* parameter value.
|
|
83
|
+
*/
|
|
84
|
+
CAD_SEPARATOR: ',',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/*
|
|
88
|
+
* Names of events that are special to jsaction. These are not all
|
|
89
|
+
* event types that are legal to use in either HTML or the addEvent()
|
|
90
|
+
* API, but these are the ones that are treated specially. All other
|
|
91
|
+
* DOM events can be used in either addEvent() or in the value of the
|
|
92
|
+
* jsaction attribute. Beware of browser specific events or events
|
|
93
|
+
* that don't bubble though: If they are not mentioned here, then
|
|
94
|
+
* event contract doesn't work around their peculiarities.
|
|
95
|
+
*/
|
|
96
|
+
const EventType = {
|
|
97
|
+
/**
|
|
98
|
+
* Mouse middle click, introduced in Chrome 55 and not yet supported on
|
|
99
|
+
* other browsers.
|
|
100
|
+
*/
|
|
101
|
+
AUXCLICK: 'auxclick',
|
|
229
102
|
/**
|
|
230
103
|
* The change event fired by browsers when the `value` attribute of input,
|
|
231
104
|
* select, and textarea elements are changed.
|
|
@@ -447,1058 +320,1053 @@ const EventType = {
|
|
|
447
320
|
*/
|
|
448
321
|
CUSTOM: '_custom',
|
|
449
322
|
};
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
// synthesize the mouseenter and mouseleave events from mouseover and
|
|
520
|
-
// mouseout events, respectively. Cf. eventcontract.js.
|
|
521
|
-
if (eventType === EventType.MOUSEENTER) {
|
|
522
|
-
return EventType.MOUSEOVER;
|
|
523
|
-
}
|
|
524
|
-
else if (eventType === EventType.MOUSELEAVE) {
|
|
525
|
-
return EventType.MOUSEOUT;
|
|
526
|
-
}
|
|
527
|
-
else if (eventType === EventType.POINTERENTER) {
|
|
528
|
-
return EventType.POINTEROVER;
|
|
529
|
-
}
|
|
530
|
-
else if (eventType === EventType.POINTERLEAVE) {
|
|
531
|
-
return EventType.POINTEROUT;
|
|
532
|
-
}
|
|
533
|
-
return eventType;
|
|
534
|
-
}
|
|
323
|
+
const NON_BUBBLING_MOUSE_EVENTS = [
|
|
324
|
+
EventType.MOUSEENTER,
|
|
325
|
+
EventType.MOUSELEAVE,
|
|
326
|
+
'pointerenter',
|
|
327
|
+
'pointerleave',
|
|
328
|
+
];
|
|
329
|
+
/**
|
|
330
|
+
* Detects whether a given event type is supported by JSAction.
|
|
331
|
+
*/
|
|
332
|
+
const isSupportedEvent = (eventType) => SUPPORTED_EVENTS.includes(eventType);
|
|
333
|
+
const SUPPORTED_EVENTS = [
|
|
334
|
+
EventType.CLICK,
|
|
335
|
+
EventType.DBLCLICK,
|
|
336
|
+
EventType.FOCUS,
|
|
337
|
+
EventType.FOCUSIN,
|
|
338
|
+
EventType.BLUR,
|
|
339
|
+
EventType.ERROR,
|
|
340
|
+
EventType.FOCUSOUT,
|
|
341
|
+
EventType.KEYDOWN,
|
|
342
|
+
EventType.KEYUP,
|
|
343
|
+
EventType.KEYPRESS,
|
|
344
|
+
EventType.LOAD,
|
|
345
|
+
EventType.MOUSEOVER,
|
|
346
|
+
EventType.MOUSEOUT,
|
|
347
|
+
EventType.SUBMIT,
|
|
348
|
+
EventType.TOGGLE,
|
|
349
|
+
EventType.TOUCHSTART,
|
|
350
|
+
EventType.TOUCHEND,
|
|
351
|
+
EventType.TOUCHMOVE,
|
|
352
|
+
'touchcancel',
|
|
353
|
+
'auxclick',
|
|
354
|
+
'change',
|
|
355
|
+
'compositionstart',
|
|
356
|
+
'compositionupdate',
|
|
357
|
+
'compositionend',
|
|
358
|
+
'beforeinput',
|
|
359
|
+
'input',
|
|
360
|
+
'select',
|
|
361
|
+
'copy',
|
|
362
|
+
'cut',
|
|
363
|
+
'paste',
|
|
364
|
+
'mousedown',
|
|
365
|
+
'mouseup',
|
|
366
|
+
'wheel',
|
|
367
|
+
'contextmenu',
|
|
368
|
+
'dragover',
|
|
369
|
+
'dragenter',
|
|
370
|
+
'dragleave',
|
|
371
|
+
'drop',
|
|
372
|
+
'dragstart',
|
|
373
|
+
'dragend',
|
|
374
|
+
'pointerdown',
|
|
375
|
+
'pointermove',
|
|
376
|
+
'pointerup',
|
|
377
|
+
'pointercancel',
|
|
378
|
+
'pointerover',
|
|
379
|
+
'pointerout',
|
|
380
|
+
'gotpointercapture',
|
|
381
|
+
'lostpointercapture',
|
|
382
|
+
// Video events.
|
|
383
|
+
'ended',
|
|
384
|
+
'loadedmetadata',
|
|
385
|
+
// Page visibility events.
|
|
386
|
+
'pagehide',
|
|
387
|
+
'pageshow',
|
|
388
|
+
'visibilitychange',
|
|
389
|
+
// Content visibility events.
|
|
390
|
+
'beforematch',
|
|
391
|
+
];
|
|
535
392
|
/**
|
|
536
|
-
* Registers the event handler function with the given DOM element for
|
|
537
|
-
* the given event type.
|
|
538
393
|
*
|
|
539
|
-
*
|
|
540
|
-
* @param eventType The event type.
|
|
541
|
-
* @param handler The handler function to install.
|
|
542
|
-
* @return Information needed to uninstall the event handler eventually.
|
|
543
|
-
*/
|
|
544
|
-
function addEventListener(element, eventType, handler) {
|
|
545
|
-
// All event handlers are registered in the bubbling
|
|
546
|
-
// phase.
|
|
547
|
-
//
|
|
548
|
-
// All browsers support focus and blur, but these events only are propagated
|
|
549
|
-
// in the capture phase. Very legacy browsers do not support focusin or
|
|
550
|
-
// focusout.
|
|
551
|
-
//
|
|
552
|
-
// It would be a bad idea to register all event handlers in the
|
|
553
|
-
// capture phase because then regular onclick handlers would not be
|
|
554
|
-
// executed at all on events that trigger a jsaction. That's not
|
|
555
|
-
// entirely what we want, at least for now.
|
|
556
|
-
//
|
|
557
|
-
// Error and load events (i.e. on images) do not bubble so they are also
|
|
558
|
-
// handled in the capture phase.
|
|
559
|
-
let capture = false;
|
|
560
|
-
if (eventType === EventType.FOCUS ||
|
|
561
|
-
eventType === EventType.BLUR ||
|
|
562
|
-
eventType === EventType.ERROR ||
|
|
563
|
-
eventType === EventType.LOAD ||
|
|
564
|
-
eventType === EventType.TOGGLE) {
|
|
565
|
-
capture = true;
|
|
566
|
-
}
|
|
567
|
-
element.addEventListener(eventType, handler, capture);
|
|
568
|
-
return { eventType, handler, capture };
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Removes the event handler for the given event from the element.
|
|
572
|
-
* the given event type.
|
|
394
|
+
* Decides whether or not an event type is an event that only has a capture phase.
|
|
573
395
|
*
|
|
574
|
-
* @param
|
|
575
|
-
* @
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// tslint:disable-next-line:no-any
|
|
587
|
-
element.detachEvent(`on${info.eventType}`, info.handler);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Cancels propagation of an event.
|
|
592
|
-
* @param e The event to cancel propagation for.
|
|
593
|
-
*/
|
|
594
|
-
function stopPropagation(e) {
|
|
595
|
-
e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* Prevents the default action of an event.
|
|
599
|
-
* @param e The event to prevent the default action for.
|
|
600
|
-
*/
|
|
601
|
-
function preventDefault(e) {
|
|
602
|
-
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Gets the target Element of the event. In Firefox, a text node may appear as
|
|
606
|
-
* the target of the event, in which case we return the parent element of the
|
|
607
|
-
* text node.
|
|
608
|
-
* @param e The event to get the target of.
|
|
609
|
-
* @return The target element.
|
|
610
|
-
*/
|
|
611
|
-
function getTarget(e) {
|
|
612
|
-
let el = e.target;
|
|
613
|
-
// In Firefox, the event may have a text node as its target. We always
|
|
614
|
-
// want the parent Element the text node belongs to, however.
|
|
615
|
-
if (!el.getAttribute && el.parentNode) {
|
|
616
|
-
el = el.parentNode;
|
|
617
|
-
}
|
|
618
|
-
return el;
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Whether we are on a Mac. Not pulling in useragent just for this.
|
|
622
|
-
*/
|
|
623
|
-
let isMac = typeof navigator !== 'undefined' && /Macintosh/.test(navigator.userAgent);
|
|
624
|
-
/**
|
|
625
|
-
* Determines and returns whether the given event (which is assumed to be a
|
|
626
|
-
* click event) is a middle click.
|
|
627
|
-
* NOTE: There is not a consistent way to identify middle click
|
|
628
|
-
* http://www.unixpapa.com/js/mouse.html
|
|
629
|
-
*/
|
|
630
|
-
function isMiddleClick(e) {
|
|
631
|
-
return (
|
|
632
|
-
// `which` is an old DOM API.
|
|
633
|
-
// tslint:disable-next-line:no-any
|
|
634
|
-
e.which === 2 ||
|
|
635
|
-
// `which` is an old DOM API.
|
|
636
|
-
// tslint:disable-next-line:no-any
|
|
637
|
-
(e.which == null &&
|
|
638
|
-
// `button` is an old DOM API.
|
|
639
|
-
// tslint:disable-next-line:no-any
|
|
640
|
-
e.button === 4) // middle click for IE
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Determines and returns whether the given event (which is assumed
|
|
645
|
-
* to be a click event) is modified. A middle click is considered a modified
|
|
646
|
-
* click to retain the default browser action, which opens a link in a new tab.
|
|
647
|
-
* @param e The event.
|
|
648
|
-
* @return Whether the given event is modified.
|
|
649
|
-
*/
|
|
650
|
-
function isModifiedClickEvent(e) {
|
|
651
|
-
return (
|
|
652
|
-
// `metaKey` is an old DOM API.
|
|
653
|
-
// tslint:disable-next-line:no-any
|
|
654
|
-
(isMac && e.metaKey) ||
|
|
655
|
-
// `ctrlKey` is an old DOM API.
|
|
656
|
-
// tslint:disable-next-line:no-any
|
|
657
|
-
(!isMac && e.ctrlKey) ||
|
|
658
|
-
isMiddleClick(e) ||
|
|
659
|
-
// `shiftKey` is an old DOM API.
|
|
660
|
-
// tslint:disable-next-line:no-any
|
|
661
|
-
e.shiftKey);
|
|
662
|
-
}
|
|
663
|
-
/** Whether we are on WebKit (e.g., Chrome). */
|
|
664
|
-
const isWebKit = typeof navigator !== 'undefined' &&
|
|
665
|
-
!/Opera/.test(navigator.userAgent) &&
|
|
666
|
-
/WebKit/.test(navigator.userAgent);
|
|
667
|
-
/** Whether we are on IE. */
|
|
668
|
-
const isIe = typeof navigator !== 'undefined' &&
|
|
669
|
-
(/MSIE/.test(navigator.userAgent) || /Trident/.test(navigator.userAgent));
|
|
670
|
-
/** Whether we are on Gecko (e.g., Firefox). */
|
|
671
|
-
const isGecko = typeof navigator !== 'undefined' &&
|
|
672
|
-
!/Opera|WebKit/.test(navigator.userAgent) &&
|
|
673
|
-
/Gecko/.test(navigator.product);
|
|
396
|
+
* @param eventType
|
|
397
|
+
* @returns bool
|
|
398
|
+
*/
|
|
399
|
+
const isCaptureEvent = (eventType) => CAPTURE_EVENTS.indexOf(eventType) >= 0;
|
|
400
|
+
const CAPTURE_EVENTS = [
|
|
401
|
+
EventType.FOCUS,
|
|
402
|
+
EventType.BLUR,
|
|
403
|
+
EventType.ERROR,
|
|
404
|
+
EventType.LOAD,
|
|
405
|
+
EventType.TOGGLE,
|
|
406
|
+
];
|
|
407
|
+
|
|
674
408
|
/**
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
409
|
+
* The parsed value of the jsaction attribute is stored in this
|
|
410
|
+
* property on the DOM node. The parsed value is an Object. The
|
|
411
|
+
* property names of the object are the events; the values are the
|
|
412
|
+
* names of the actions. This property is attached even on nodes
|
|
413
|
+
* that don't have a jsaction attribute as an optimization, because
|
|
414
|
+
* property lookup is faster than attribute access.
|
|
679
415
|
*/
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
if (isTextControl(el)) {
|
|
685
|
-
return false;
|
|
686
|
-
}
|
|
687
|
-
if (isNativelyActivatable(el)) {
|
|
688
|
-
return false;
|
|
689
|
-
}
|
|
690
|
-
// `isContentEditable` is an old DOM API.
|
|
691
|
-
// tslint:disable-next-line:no-any
|
|
692
|
-
if (el.isContentEditable) {
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
return true;
|
|
696
|
-
}
|
|
416
|
+
const JSACTION = '__jsaction';
|
|
417
|
+
/** The value of the oi attribute as a property, for faster access. */
|
|
418
|
+
const OI = '__oi';
|
|
697
419
|
/**
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
420
|
+
* The owner property references an a logical owner for a DOM node. JSAction
|
|
421
|
+
* will follow this reference instead of parentNode when traversing the DOM
|
|
422
|
+
* to find jsaction attributes. This allows overlaying a logical structure
|
|
423
|
+
* over a document where the DOM structure can't reflect that structure.
|
|
701
424
|
*/
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
// `altKey` is an old DOM API.
|
|
711
|
-
// tslint:disable-next-line:no-any
|
|
712
|
-
e.altKey ||
|
|
713
|
-
// `metaKey` is an old DOM API.
|
|
714
|
-
// tslint:disable-next-line:no-any
|
|
715
|
-
e.metaKey);
|
|
716
|
-
}
|
|
425
|
+
const OWNER = '__owner';
|
|
426
|
+
/** All properties that are used by jsaction. */
|
|
427
|
+
const Property = {
|
|
428
|
+
JSACTION,
|
|
429
|
+
OI,
|
|
430
|
+
OWNER,
|
|
431
|
+
};
|
|
432
|
+
|
|
717
433
|
/**
|
|
718
|
-
*
|
|
719
|
-
* has event handlers attached because it is a native HTML control. Used to
|
|
720
|
-
* determine if preventDefault should be called when isActionKeyEvent is true.
|
|
721
|
-
* @param e The event.
|
|
722
|
-
* @return If preventDefault should be called.
|
|
434
|
+
* Map from jsaction annotation to a parsed map from event name to action name.
|
|
723
435
|
*/
|
|
724
|
-
|
|
725
|
-
const el = getTarget(e);
|
|
726
|
-
const tagName = el.tagName.toUpperCase();
|
|
727
|
-
const role = (el.getAttribute('role') || '').toUpperCase();
|
|
728
|
-
if (tagName === 'BUTTON' || role === 'BUTTON') {
|
|
729
|
-
return true;
|
|
730
|
-
}
|
|
731
|
-
if (!isNativeHTMLControl(el)) {
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
if (tagName === 'A') {
|
|
735
|
-
return false;
|
|
736
|
-
}
|
|
737
|
-
/**
|
|
738
|
-
* Fix for physical d-pads on feature phone platforms; the native event
|
|
739
|
-
* (ie. isTrusted: true) needs to fire to show the OPTION list. See
|
|
740
|
-
* b/135288469 for more info.
|
|
741
|
-
*/
|
|
742
|
-
if (tagName === 'SELECT') {
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
if (processSpace(el)) {
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
if (isTextControl(el)) {
|
|
749
|
-
return false;
|
|
750
|
-
}
|
|
751
|
-
return true;
|
|
752
|
-
}
|
|
436
|
+
const parseCache = {};
|
|
753
437
|
/**
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
* Space key.
|
|
759
|
-
* @param e The event.
|
|
760
|
-
* @return True, if the event emulates a DOM click.
|
|
438
|
+
* Reads the jsaction parser cache from the given DOM Element.
|
|
439
|
+
*
|
|
440
|
+
* @param element .
|
|
441
|
+
* @return Map from event to qualified name of the jsaction bound to it.
|
|
761
442
|
*/
|
|
762
|
-
function
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
// tslint:disable-next-line:no-any
|
|
766
|
-
e.which ||
|
|
767
|
-
// `keyCode` is an old DOM API.
|
|
768
|
-
// tslint:disable-next-line:no-any
|
|
769
|
-
e.keyCode;
|
|
770
|
-
if (!key && e.key) {
|
|
771
|
-
key = ACTION_KEY_TO_KEYCODE[e.key];
|
|
772
|
-
}
|
|
773
|
-
if (isWebKit && key === KeyCode.MAC_ENTER) {
|
|
774
|
-
key = KeyCode.ENTER;
|
|
775
|
-
}
|
|
776
|
-
if (key !== KeyCode.ENTER && key !== KeyCode.SPACE) {
|
|
777
|
-
return false;
|
|
778
|
-
}
|
|
779
|
-
const el = getTarget(e);
|
|
780
|
-
if (e.type !== EventType.KEYDOWN || !isValidActionKeyTarget(el) || hasModifierKey(e)) {
|
|
781
|
-
return false;
|
|
782
|
-
}
|
|
783
|
-
// For <input type="checkbox">, we must only handle the browser's native click
|
|
784
|
-
// event, so that the browser can toggle the checkbox.
|
|
785
|
-
if (processSpace(el) && key === KeyCode.SPACE) {
|
|
786
|
-
return false;
|
|
787
|
-
}
|
|
788
|
-
// If this element is non-focusable, ignore stray keystrokes (b/18337209)
|
|
789
|
-
// Sscreen readers can move without tab focus, so any tabIndex is focusable.
|
|
790
|
-
// See B/21809604
|
|
791
|
-
if (!isFocusable(el)) {
|
|
792
|
-
return false;
|
|
793
|
-
}
|
|
794
|
-
const type = (el.getAttribute('role') ||
|
|
795
|
-
el.type ||
|
|
796
|
-
el.tagName).toUpperCase();
|
|
797
|
-
const isSpecificTriggerKey = IDENTIFIER_TO_KEY_TRIGGER_MAPPING[type] % key === 0;
|
|
798
|
-
const isDefaultTriggerKey = !(type in IDENTIFIER_TO_KEY_TRIGGER_MAPPING) && key === KeyCode.ENTER;
|
|
799
|
-
const hasType = el.tagName.toUpperCase() !== 'INPUT' || !!el.type;
|
|
800
|
-
return (isSpecificTriggerKey || isDefaultTriggerKey) && hasType;
|
|
443
|
+
function get(element) {
|
|
444
|
+
// @ts-ignore
|
|
445
|
+
return element[JSACTION];
|
|
801
446
|
}
|
|
802
447
|
/**
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
448
|
+
* Writes the jsaction parser cache to the given DOM Element.
|
|
449
|
+
*
|
|
450
|
+
* @param element .
|
|
451
|
+
* @param actionMap Map from event to qualified name of the jsaction bound to
|
|
452
|
+
* it.
|
|
806
453
|
*/
|
|
807
|
-
function
|
|
808
|
-
|
|
809
|
-
|
|
454
|
+
function set(element, actionMap) {
|
|
455
|
+
// @ts-ignore
|
|
456
|
+
element[JSACTION] = actionMap;
|
|
810
457
|
}
|
|
811
458
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
459
|
+
* Looks up the parsed action map from the source jsaction attribute value.
|
|
460
|
+
*
|
|
461
|
+
* @param text Unparsed jsaction attribute value.
|
|
462
|
+
* @return Parsed jsaction attribute value, if already present in the cache.
|
|
814
463
|
*/
|
|
815
|
-
function
|
|
816
|
-
|
|
817
|
-
// which returns an object with a 'specified' property if tabIndex is
|
|
818
|
-
// specified. This works on other browsers, too.
|
|
819
|
-
const attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
|
|
820
|
-
return attrNode != null && attrNode.specified;
|
|
821
|
-
}
|
|
822
|
-
/** Element tagnames that are focusable by default. */
|
|
823
|
-
const NATIVELY_FOCUSABLE_ELEMENTS = {
|
|
824
|
-
'A': 1,
|
|
825
|
-
'INPUT': 1,
|
|
826
|
-
'TEXTAREA': 1,
|
|
827
|
-
'SELECT': 1,
|
|
828
|
-
'BUTTON': 1,
|
|
829
|
-
};
|
|
830
|
-
/** @return True, if the Space key was pressed. */
|
|
831
|
-
function isSpaceKeyEvent(e) {
|
|
832
|
-
const key =
|
|
833
|
-
// `which` is an old DOM API.
|
|
834
|
-
// tslint:disable-next-line:no-any
|
|
835
|
-
e.which ||
|
|
836
|
-
// `keyCode` is an old DOM API.
|
|
837
|
-
// tslint:disable-next-line:no-any
|
|
838
|
-
e.keyCode;
|
|
839
|
-
const el = getTarget(e);
|
|
840
|
-
const elementName = (el.type || el.tagName).toUpperCase();
|
|
841
|
-
return key === KeyCode.SPACE && elementName !== 'CHECKBOX';
|
|
464
|
+
function getParsed(text) {
|
|
465
|
+
return parseCache[text];
|
|
842
466
|
}
|
|
843
467
|
/**
|
|
844
|
-
*
|
|
845
|
-
* event type (mouseenter, mouseleave, pointerenter, and pointerleave).
|
|
846
|
-
*
|
|
847
|
-
* During mouseover (mouseenter) and pointerover (pointerenter), the
|
|
848
|
-
* relatedTarget is the element being entered from. During mouseout (mouseleave)
|
|
849
|
-
* and pointerout (pointerleave), the relatedTarget is the element being exited
|
|
850
|
-
* to.
|
|
468
|
+
* Inserts the parse result for the given source jsaction value into the cache.
|
|
851
469
|
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
470
|
+
* @param text Unparsed jsaction attribute value.
|
|
471
|
+
* @param parsed Attribute value parsed into the action map.
|
|
472
|
+
*/
|
|
473
|
+
function setParsed(text, parsed) {
|
|
474
|
+
parseCache[text] = parsed;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Clears the jsaction parser cache from the given DOM Element.
|
|
854
478
|
*
|
|
855
|
-
* @param
|
|
856
|
-
* @param type The type of the mouse special event.
|
|
857
|
-
* @param element The element on which the jsaction for the
|
|
858
|
-
* mouseenter/mouseleave event is defined.
|
|
859
|
-
* @return True if the event is a mouseenter/mouseleave event.
|
|
479
|
+
* @param element .
|
|
860
480
|
*/
|
|
861
|
-
function
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
481
|
+
function clear(element) {
|
|
482
|
+
if (JSACTION in element) {
|
|
483
|
+
delete element[JSACTION];
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** Added for readability when accessing stable property names. */
|
|
488
|
+
function getEventType(eventInfo) {
|
|
489
|
+
return eventInfo.eventType;
|
|
490
|
+
}
|
|
491
|
+
/** Added for readability when accessing stable property names. */
|
|
492
|
+
function setEventType(eventInfo, eventType) {
|
|
493
|
+
eventInfo.eventType = eventType;
|
|
494
|
+
}
|
|
495
|
+
/** Added for readability when accessing stable property names. */
|
|
496
|
+
function getEvent(eventInfo) {
|
|
497
|
+
return eventInfo.event;
|
|
498
|
+
}
|
|
499
|
+
/** Added for readability when accessing stable property names. */
|
|
500
|
+
function setEvent(eventInfo, event) {
|
|
501
|
+
eventInfo.event = event;
|
|
502
|
+
}
|
|
503
|
+
/** Added for readability when accessing stable property names. */
|
|
504
|
+
function getTargetElement(eventInfo) {
|
|
505
|
+
return eventInfo.targetElement;
|
|
506
|
+
}
|
|
507
|
+
/** Added for readability when accessing stable property names. */
|
|
508
|
+
function setTargetElement(eventInfo, targetElement) {
|
|
509
|
+
eventInfo.targetElement = targetElement;
|
|
510
|
+
}
|
|
511
|
+
/** Added for readability when accessing stable property names. */
|
|
512
|
+
function getContainer(eventInfo) {
|
|
513
|
+
return eventInfo.eic;
|
|
514
|
+
}
|
|
515
|
+
/** Added for readability when accessing stable property names. */
|
|
516
|
+
function setContainer(eventInfo, container) {
|
|
517
|
+
eventInfo.eic = container;
|
|
518
|
+
}
|
|
519
|
+
/** Added for readability when accessing stable property names. */
|
|
520
|
+
function getTimestamp(eventInfo) {
|
|
521
|
+
return eventInfo.timeStamp;
|
|
522
|
+
}
|
|
523
|
+
/** Added for readability when accessing stable property names. */
|
|
524
|
+
function setTimestamp(eventInfo, timestamp) {
|
|
525
|
+
eventInfo.timeStamp = timestamp;
|
|
526
|
+
}
|
|
527
|
+
/** Added for readability when accessing stable property names. */
|
|
528
|
+
function getAction(eventInfo) {
|
|
529
|
+
return eventInfo.eia;
|
|
530
|
+
}
|
|
531
|
+
/** Added for readability when accessing stable property names. */
|
|
532
|
+
function setAction(eventInfo, actionName, actionElement) {
|
|
533
|
+
eventInfo.eia = [actionName, actionElement];
|
|
534
|
+
}
|
|
535
|
+
/** Added for readability when accessing stable property names. */
|
|
536
|
+
function unsetAction(eventInfo) {
|
|
537
|
+
eventInfo.eia = undefined;
|
|
538
|
+
}
|
|
539
|
+
/** Added for readability when accessing stable property names. */
|
|
540
|
+
function getActionName(actionInfo) {
|
|
541
|
+
return actionInfo[0];
|
|
542
|
+
}
|
|
543
|
+
/** Added for readability when accessing stable property names. */
|
|
544
|
+
function getActionElement(actionInfo) {
|
|
545
|
+
return actionInfo[1];
|
|
546
|
+
}
|
|
547
|
+
/** Added for readability when accessing stable property names. */
|
|
548
|
+
function getIsReplay(eventInfo) {
|
|
549
|
+
return eventInfo.eirp;
|
|
550
|
+
}
|
|
551
|
+
/** Added for readability when accessing stable property names. */
|
|
552
|
+
function setIsReplay(eventInfo, replay) {
|
|
553
|
+
eventInfo.eirp = replay;
|
|
554
|
+
}
|
|
555
|
+
/** Added for readability when accessing stable property names. */
|
|
556
|
+
function getA11yClickKey(eventInfo) {
|
|
557
|
+
return eventInfo.eiack;
|
|
558
|
+
}
|
|
559
|
+
/** Added for readability when accessing stable property names. */
|
|
560
|
+
function setA11yClickKey(eventInfo, a11yClickKey) {
|
|
561
|
+
eventInfo.eiack = a11yClickKey;
|
|
562
|
+
}
|
|
563
|
+
/** Added for readability when accessing stable property names. */
|
|
564
|
+
function getResolved(eventInfo) {
|
|
565
|
+
return eventInfo.eir;
|
|
566
|
+
}
|
|
567
|
+
/** Added for readability when accessing stable property names. */
|
|
568
|
+
function setResolved(eventInfo, resolved) {
|
|
569
|
+
eventInfo.eir = resolved;
|
|
570
|
+
}
|
|
571
|
+
/** Clones an `EventInfo` */
|
|
572
|
+
function cloneEventInfo(eventInfo) {
|
|
573
|
+
return {
|
|
574
|
+
eventType: eventInfo.eventType,
|
|
575
|
+
event: eventInfo.event,
|
|
576
|
+
targetElement: eventInfo.targetElement,
|
|
577
|
+
eic: eventInfo.eic,
|
|
578
|
+
eia: eventInfo.eia,
|
|
579
|
+
timeStamp: eventInfo.timeStamp,
|
|
580
|
+
eirp: eventInfo.eirp,
|
|
581
|
+
eiack: eventInfo.eiack,
|
|
582
|
+
eir: eventInfo.eir,
|
|
583
|
+
};
|
|
870
584
|
}
|
|
871
585
|
/**
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
* event is defined.
|
|
877
|
-
* @return A modified event-like object copied from the event object passed into
|
|
878
|
-
* this function.
|
|
586
|
+
* Utility function for creating an `EventInfo`.
|
|
587
|
+
*
|
|
588
|
+
* This can be used from code-size sensitive compilation units, as taking
|
|
589
|
+
* parameters vs. an `Object` literal reduces code size.
|
|
879
590
|
*/
|
|
880
|
-
function
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
// tslint:disable-next-line:no-any
|
|
892
|
-
const copy = {};
|
|
893
|
-
for (const property in e) {
|
|
894
|
-
if (property === 'srcElement' || property === 'target') {
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
const key = property;
|
|
898
|
-
// Making a copy requires iterating through all properties of `Event`.
|
|
899
|
-
// tslint:disable-next-line:no-dict-access-on-struct-type
|
|
900
|
-
const value = e[key];
|
|
901
|
-
if (typeof value === 'function') {
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
// Value should be the expected type, but the value of `key` is not known
|
|
905
|
-
// statically.
|
|
906
|
-
// tslint:disable-next-line:no-any
|
|
907
|
-
copy[key] = value;
|
|
908
|
-
}
|
|
909
|
-
if (e.type === EventType.MOUSEOVER) {
|
|
910
|
-
copy['type'] = EventType.MOUSEENTER;
|
|
911
|
-
}
|
|
912
|
-
else if (e.type === EventType.MOUSEOUT) {
|
|
913
|
-
copy['type'] = EventType.MOUSELEAVE;
|
|
914
|
-
}
|
|
915
|
-
else if (e.type === EventType.POINTEROVER) {
|
|
916
|
-
copy['type'] = EventType.POINTERENTER;
|
|
917
|
-
}
|
|
918
|
-
else {
|
|
919
|
-
copy['type'] = EventType.POINTERLEAVE;
|
|
920
|
-
}
|
|
921
|
-
copy['target'] = copy['srcElement'] = target;
|
|
922
|
-
copy['bubbles'] = false;
|
|
923
|
-
return copy;
|
|
591
|
+
function createEventInfoFromParameters(eventType, event, targetElement, container, timestamp, action, isReplay, a11yClickKey) {
|
|
592
|
+
return {
|
|
593
|
+
eventType,
|
|
594
|
+
event,
|
|
595
|
+
targetElement,
|
|
596
|
+
eic: container,
|
|
597
|
+
timeStamp: timestamp,
|
|
598
|
+
eia: action,
|
|
599
|
+
eirp: isReplay,
|
|
600
|
+
eiack: a11yClickKey,
|
|
601
|
+
};
|
|
924
602
|
}
|
|
925
603
|
/**
|
|
926
|
-
*
|
|
927
|
-
* and screenY. If the event has no touch information at all, the returned
|
|
928
|
-
* value is null.
|
|
929
|
-
*
|
|
930
|
-
* The fields of this Object are unquoted.
|
|
604
|
+
* Utility function for creating an `EventInfo`.
|
|
931
605
|
*
|
|
932
|
-
*
|
|
606
|
+
* This should be used in compilation units that are less sensitive to code
|
|
607
|
+
* size.
|
|
933
608
|
*/
|
|
934
|
-
function
|
|
935
|
-
const touch = (event.changedTouches && event.changedTouches[0]) || (event.touches && event.touches[0]);
|
|
936
|
-
if (!touch) {
|
|
937
|
-
return null;
|
|
938
|
-
}
|
|
609
|
+
function createEventInfo({ eventType, event, targetElement, container, timestamp, action, isReplay, a11yClickKey, }) {
|
|
939
610
|
return {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
611
|
+
eventType,
|
|
612
|
+
event,
|
|
613
|
+
targetElement,
|
|
614
|
+
eic: container,
|
|
615
|
+
timeStamp: timestamp,
|
|
616
|
+
eia: action ? [action.name, action.element] : undefined,
|
|
617
|
+
eirp: isReplay,
|
|
618
|
+
eiack: a11yClickKey,
|
|
944
619
|
};
|
|
945
620
|
}
|
|
946
621
|
/**
|
|
947
|
-
*
|
|
948
|
-
* original corresponding "touchend" event for a fast-click implementation.
|
|
949
|
-
*
|
|
950
|
-
* It takes a touch event, adds common fields found in a click event and
|
|
951
|
-
* changes the type to 'click', so that the resulting event looks more like
|
|
952
|
-
* a real click event.
|
|
622
|
+
* Utility class around an `EventInfo`.
|
|
953
623
|
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
* this function.
|
|
624
|
+
* This should be used in compilation units that are less sensitive to code
|
|
625
|
+
* size.
|
|
957
626
|
*/
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
click['type'] = EventType.CLICK;
|
|
962
|
-
for (const property in event) {
|
|
963
|
-
if (property === 'type' || property === 'srcElement') {
|
|
964
|
-
continue;
|
|
965
|
-
}
|
|
966
|
-
const key = property;
|
|
967
|
-
// Making a copy requires iterating through all properties of `TouchEvent`.
|
|
968
|
-
// tslint:disable-next-line:no-dict-access-on-struct-type
|
|
969
|
-
const value = event[key];
|
|
970
|
-
if (typeof value === 'function') {
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
// Value should be the expected type, but the value of `key` is not known
|
|
974
|
-
// statically.
|
|
975
|
-
// tslint:disable-next-line:no-any
|
|
976
|
-
click[key] = value;
|
|
627
|
+
class EventInfoWrapper {
|
|
628
|
+
constructor(eventInfo) {
|
|
629
|
+
this.eventInfo = eventInfo;
|
|
977
630
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
click['timeStamp'] = Date.now();
|
|
981
|
-
// Emulate preventDefault and stopPropagation behavior
|
|
982
|
-
click['defaultPrevented'] = false;
|
|
983
|
-
click['preventDefault'] = syntheticPreventDefault;
|
|
984
|
-
click['_propagationStopped'] = false;
|
|
985
|
-
click['stopPropagation'] = syntheticStopPropagation;
|
|
986
|
-
// Emulate click coordinates using touch info
|
|
987
|
-
const touch = getTouchData(event);
|
|
988
|
-
if (touch) {
|
|
989
|
-
click['clientX'] = touch.clientX;
|
|
990
|
-
click['clientY'] = touch.clientY;
|
|
991
|
-
click['screenX'] = touch.screenX;
|
|
992
|
-
click['screenY'] = touch.screenY;
|
|
631
|
+
getEventType() {
|
|
632
|
+
return getEventType(this.eventInfo);
|
|
993
633
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
634
|
+
setEventType(eventType) {
|
|
635
|
+
setEventType(this.eventInfo, eventType);
|
|
636
|
+
}
|
|
637
|
+
getEvent() {
|
|
638
|
+
return getEvent(this.eventInfo);
|
|
639
|
+
}
|
|
640
|
+
setEvent(event) {
|
|
641
|
+
setEvent(this.eventInfo, event);
|
|
642
|
+
}
|
|
643
|
+
getTargetElement() {
|
|
644
|
+
return getTargetElement(this.eventInfo);
|
|
645
|
+
}
|
|
646
|
+
setTargetElement(targetElement) {
|
|
647
|
+
setTargetElement(this.eventInfo, targetElement);
|
|
648
|
+
}
|
|
649
|
+
getContainer() {
|
|
650
|
+
return getContainer(this.eventInfo);
|
|
651
|
+
}
|
|
652
|
+
setContainer(container) {
|
|
653
|
+
setContainer(this.eventInfo, container);
|
|
654
|
+
}
|
|
655
|
+
getTimestamp() {
|
|
656
|
+
return getTimestamp(this.eventInfo);
|
|
657
|
+
}
|
|
658
|
+
setTimestamp(timestamp) {
|
|
659
|
+
setTimestamp(this.eventInfo, timestamp);
|
|
660
|
+
}
|
|
661
|
+
getAction() {
|
|
662
|
+
const action = getAction(this.eventInfo);
|
|
663
|
+
if (!action)
|
|
664
|
+
return undefined;
|
|
665
|
+
return {
|
|
666
|
+
name: action[0],
|
|
667
|
+
element: action[1],
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
setAction(action) {
|
|
671
|
+
if (!action) {
|
|
672
|
+
unsetAction(this.eventInfo);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
setAction(this.eventInfo, action.name, action.element);
|
|
676
|
+
}
|
|
677
|
+
getIsReplay() {
|
|
678
|
+
return getIsReplay(this.eventInfo);
|
|
679
|
+
}
|
|
680
|
+
setIsReplay(replay) {
|
|
681
|
+
setIsReplay(this.eventInfo, replay);
|
|
682
|
+
}
|
|
683
|
+
getResolved() {
|
|
684
|
+
return getResolved(this.eventInfo);
|
|
685
|
+
}
|
|
686
|
+
setResolved(resolved) {
|
|
687
|
+
setResolved(this.eventInfo, resolved);
|
|
688
|
+
}
|
|
689
|
+
clone() {
|
|
690
|
+
return new EventInfoWrapper(cloneEventInfo(this.eventInfo));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
1047
694
|
/**
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
* @param
|
|
1051
|
-
* @
|
|
695
|
+
* Determines if one node is contained within another. Adapted from
|
|
696
|
+
* {@see goog.dom.contains}.
|
|
697
|
+
* @param node Node that should contain otherNode.
|
|
698
|
+
* @param otherNode Node being contained.
|
|
699
|
+
* @return True if otherNode is contained within node.
|
|
1052
700
|
*/
|
|
1053
|
-
function
|
|
1054
|
-
|
|
1055
|
-
|
|
701
|
+
function contains(node, otherNode) {
|
|
702
|
+
if (otherNode === null) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
// We use browser specific methods for this if available since it is faster
|
|
706
|
+
// that way.
|
|
707
|
+
// IE DOM
|
|
708
|
+
if ('contains' in node && otherNode.nodeType === 1) {
|
|
709
|
+
return node.contains(otherNode);
|
|
710
|
+
}
|
|
711
|
+
// W3C DOM Level 3
|
|
712
|
+
if ('compareDocumentPosition' in node) {
|
|
713
|
+
return node === otherNode || Boolean(node.compareDocumentPosition(otherNode) & 16);
|
|
714
|
+
}
|
|
715
|
+
// W3C DOM Level 1
|
|
716
|
+
while (otherNode && node !== otherNode) {
|
|
717
|
+
otherNode = otherNode.parentNode;
|
|
718
|
+
}
|
|
719
|
+
return otherNode === node;
|
|
1056
720
|
}
|
|
1057
721
|
/**
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
* @return Whether or not the given element is a text control.
|
|
722
|
+
* Helper method for broadcastCustomEvent. Returns true if any member of
|
|
723
|
+
* the set is an ancestor of element.
|
|
1061
724
|
*/
|
|
1062
|
-
function
|
|
1063
|
-
|
|
1064
|
-
|
|
725
|
+
function hasAncestorInNodeList(element, nodeList) {
|
|
726
|
+
for (let idx = 0; idx < nodeList.length; ++idx) {
|
|
727
|
+
const member = nodeList[idx];
|
|
728
|
+
if (member !== element && contains(member, element)) {
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return false;
|
|
1065
733
|
}
|
|
734
|
+
|
|
1066
735
|
/**
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
* @return If the given element is a native HTML control.
|
|
736
|
+
* If on a Macintosh with an extended keyboard, the Enter key located in the
|
|
737
|
+
* numeric pad has a different ASCII code.
|
|
1070
738
|
*/
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
739
|
+
const MAC_ENTER = 3;
|
|
740
|
+
/** The Enter key. */
|
|
741
|
+
const ENTER = 13;
|
|
742
|
+
/** The Space key. */
|
|
743
|
+
const SPACE = 32;
|
|
744
|
+
/** Special keycodes used by jsaction for the generic click action. */
|
|
745
|
+
const KeyCode = { MAC_ENTER, ENTER, SPACE };
|
|
746
|
+
|
|
1074
747
|
/**
|
|
1075
|
-
*
|
|
1076
|
-
* events for natively activatable elements, even when activated via keyboard.
|
|
1077
|
-
* For these elements, we don't need to raise a11y click events.
|
|
1078
|
-
* @param el The element.
|
|
1079
|
-
* @return If the given element is a native HTML control.
|
|
748
|
+
* Gets a browser event type, if it would differ from the JSAction event type.
|
|
1080
749
|
*/
|
|
1081
|
-
function
|
|
1082
|
-
|
|
1083
|
-
|
|
750
|
+
function getBrowserEventType(eventType) {
|
|
751
|
+
// Mouseenter and mouseleave events are not handled directly because they
|
|
752
|
+
// are not available everywhere. In browsers where they are available, they
|
|
753
|
+
// don't bubble and aren't visible at the container boundary. Instead, we
|
|
754
|
+
// synthesize the mouseenter and mouseleave events from mouseover and
|
|
755
|
+
// mouseout events, respectively. Cf. eventcontract.js.
|
|
756
|
+
if (eventType === EventType.MOUSEENTER) {
|
|
757
|
+
return EventType.MOUSEOVER;
|
|
758
|
+
}
|
|
759
|
+
else if (eventType === EventType.MOUSELEAVE) {
|
|
760
|
+
return EventType.MOUSEOUT;
|
|
761
|
+
}
|
|
762
|
+
else if (eventType === EventType.POINTERENTER) {
|
|
763
|
+
return EventType.POINTEROVER;
|
|
764
|
+
}
|
|
765
|
+
else if (eventType === EventType.POINTERLEAVE) {
|
|
766
|
+
return EventType.POINTEROUT;
|
|
767
|
+
}
|
|
768
|
+
return eventType;
|
|
1084
769
|
}
|
|
1085
770
|
/**
|
|
1086
|
-
*
|
|
1087
|
-
* the
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
771
|
+
* Registers the event handler function with the given DOM element for
|
|
772
|
+
* the given event type.
|
|
773
|
+
*
|
|
774
|
+
* @param element The element.
|
|
775
|
+
* @param eventType The event type.
|
|
776
|
+
* @param handler The handler function to install.
|
|
777
|
+
* @return Information needed to uninstall the event handler eventually.
|
|
1091
778
|
*/
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
'
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
'URL': true,
|
|
1115
|
-
'WEEK': true,
|
|
1116
|
-
};
|
|
1117
|
-
/** TagNames that are native HTML controls. */
|
|
1118
|
-
const NATIVE_HTML_CONTROLS = {
|
|
1119
|
-
'A': true,
|
|
1120
|
-
'AREA': true,
|
|
1121
|
-
'BUTTON': true,
|
|
1122
|
-
'DIALOG': true,
|
|
1123
|
-
'IMG': true,
|
|
1124
|
-
'INPUT': true,
|
|
1125
|
-
'LINK': true,
|
|
1126
|
-
'MENU': true,
|
|
1127
|
-
'OPTGROUP': true,
|
|
1128
|
-
'OPTION': true,
|
|
1129
|
-
'PROGRESS': true,
|
|
1130
|
-
'SELECT': true,
|
|
1131
|
-
'TEXTAREA': true,
|
|
1132
|
-
};
|
|
1133
|
-
/** Exported for testing. */
|
|
1134
|
-
const testing = {
|
|
1135
|
-
setIsMac(value) {
|
|
1136
|
-
isMac = value;
|
|
1137
|
-
},
|
|
1138
|
-
};
|
|
1139
|
-
|
|
779
|
+
function addEventListener(element, eventType, handler) {
|
|
780
|
+
// All event handlers are registered in the bubbling
|
|
781
|
+
// phase.
|
|
782
|
+
//
|
|
783
|
+
// All browsers support focus and blur, but these events only are propagated
|
|
784
|
+
// in the capture phase. Very legacy browsers do not support focusin or
|
|
785
|
+
// focusout.
|
|
786
|
+
//
|
|
787
|
+
// It would be a bad idea to register all event handlers in the
|
|
788
|
+
// capture phase because then regular onclick handlers would not be
|
|
789
|
+
// executed at all on events that trigger a jsaction. That's not
|
|
790
|
+
// entirely what we want, at least for now.
|
|
791
|
+
//
|
|
792
|
+
// Error and load events (i.e. on images) do not bubble so they are also
|
|
793
|
+
// handled in the capture phase.
|
|
794
|
+
let capture = false;
|
|
795
|
+
if (isCaptureEvent(eventType)) {
|
|
796
|
+
capture = true;
|
|
797
|
+
}
|
|
798
|
+
element.addEventListener(eventType, handler, capture);
|
|
799
|
+
return { eventType, handler, capture };
|
|
800
|
+
}
|
|
1140
801
|
/**
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
802
|
+
* Removes the event handler for the given event from the element.
|
|
803
|
+
* the given event type.
|
|
804
|
+
*
|
|
805
|
+
* @param element The element.
|
|
806
|
+
* @param info The information needed to deregister the handler, as returned by
|
|
807
|
+
* addEventListener(), above.
|
|
1144
808
|
*/
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
* dispatches them to the `eventReplayer`. Defaults to dispatching to `dispatchDelegate`.
|
|
1151
|
-
* @param dispatchDelegate A function that should handle dispatching an `EventInfoWrapper` to handlers.
|
|
1152
|
-
*/
|
|
1153
|
-
constructor(dispatchDelegate, { actionResolver, eventReplayer = createEventReplayer(dispatchDelegate), } = {}) {
|
|
1154
|
-
this.dispatchDelegate = dispatchDelegate;
|
|
1155
|
-
/** Whether the event replay is scheduled. */
|
|
1156
|
-
this.eventReplayScheduled = false;
|
|
1157
|
-
/** The queue of events. */
|
|
1158
|
-
this.replayEventInfoWrappers = [];
|
|
1159
|
-
this.actionResolver = actionResolver;
|
|
1160
|
-
this.eventReplayer = eventReplayer;
|
|
809
|
+
function removeEventListener(element, info) {
|
|
810
|
+
if (element.removeEventListener) {
|
|
811
|
+
element.removeEventListener(info.eventType, info.handler, info.capture);
|
|
812
|
+
// `detachEvent` is an old DOM API.
|
|
813
|
+
// tslint:disable-next-line:no-any
|
|
1161
814
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
* the given event. If there is no handler registered queues the event and
|
|
1167
|
-
* checks if a loader is registered for the given namespace. If so, calls it.
|
|
1168
|
-
*
|
|
1169
|
-
* Alternatively, if in global dispatch mode, calls all registered global
|
|
1170
|
-
* handlers for the appropriate event type.
|
|
1171
|
-
*
|
|
1172
|
-
* The three functionalities of this call are deliberately not split into
|
|
1173
|
-
* three methods (and then declared as an abstract interface), because the
|
|
1174
|
-
* interface is used by EventContract, which lives in a different jsbinary.
|
|
1175
|
-
* Therefore the interface between the three is defined entirely in terms that
|
|
1176
|
-
* are invariant under jscompiler processing (Function and Array, as opposed
|
|
1177
|
-
* to a custom type with method names).
|
|
1178
|
-
*
|
|
1179
|
-
* @param eventInfo The info for the event that triggered this call or the
|
|
1180
|
-
* queue of events from EventContract.
|
|
1181
|
-
*/
|
|
1182
|
-
dispatch(eventInfo) {
|
|
1183
|
-
const eventInfoWrapper = new EventInfoWrapper(eventInfo);
|
|
1184
|
-
this.actionResolver?.resolve(eventInfo);
|
|
1185
|
-
const action = eventInfoWrapper.getAction();
|
|
1186
|
-
if (action && shouldPreventDefaultBeforeDispatching(action.element, eventInfoWrapper)) {
|
|
1187
|
-
preventDefault(eventInfoWrapper.getEvent());
|
|
1188
|
-
}
|
|
1189
|
-
if (eventInfoWrapper.getIsReplay()) {
|
|
1190
|
-
this.scheduleEventInfoWrapperReplay(eventInfoWrapper);
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
this.dispatchDelegate(eventInfoWrapper);
|
|
815
|
+
else if (element.detachEvent) {
|
|
816
|
+
// `detachEvent` is an old DOM API.
|
|
817
|
+
// tslint:disable-next-line:no-any
|
|
818
|
+
element.detachEvent(`on${info.eventType}`, info.handler);
|
|
1194
819
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Cancels propagation of an event.
|
|
823
|
+
* @param e The event to cancel propagation for.
|
|
824
|
+
*/
|
|
825
|
+
function stopPropagation(e) {
|
|
826
|
+
e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Prevents the default action of an event.
|
|
830
|
+
* @param e The event to prevent the default action for.
|
|
831
|
+
*/
|
|
832
|
+
function preventDefault(e) {
|
|
833
|
+
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Gets the target Element of the event. In Firefox, a text node may appear as
|
|
837
|
+
* the target of the event, in which case we return the parent element of the
|
|
838
|
+
* text node.
|
|
839
|
+
* @param e The event to get the target of.
|
|
840
|
+
* @return The target element.
|
|
841
|
+
*/
|
|
842
|
+
function getTarget(e) {
|
|
843
|
+
let el = e.target;
|
|
844
|
+
// In Firefox, the event may have a text node as its target. We always
|
|
845
|
+
// want the parent Element the text node belongs to, however.
|
|
846
|
+
if (!el.getAttribute && el.parentNode) {
|
|
847
|
+
el = el.parentNode;
|
|
1210
848
|
}
|
|
849
|
+
return el;
|
|
1211
850
|
}
|
|
1212
851
|
/**
|
|
1213
|
-
*
|
|
1214
|
-
* the queue.
|
|
852
|
+
* Whether we are on a Mac. Not pulling in useragent just for this.
|
|
1215
853
|
*/
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
854
|
+
let isMac = typeof navigator !== 'undefined' && /Macintosh/.test(navigator.userAgent);
|
|
855
|
+
/**
|
|
856
|
+
* Determines and returns whether the given event (which is assumed to be a
|
|
857
|
+
* click event) is a middle click.
|
|
858
|
+
* NOTE: There is not a consistent way to identify middle click
|
|
859
|
+
* http://www.unixpapa.com/js/mouse.html
|
|
860
|
+
*/
|
|
861
|
+
function isMiddleClick(e) {
|
|
862
|
+
return (
|
|
863
|
+
// `which` is an old DOM API.
|
|
864
|
+
// tslint:disable-next-line:no-any
|
|
865
|
+
e.which === 2 ||
|
|
866
|
+
// `which` is an old DOM API.
|
|
867
|
+
// tslint:disable-next-line:no-any
|
|
868
|
+
(e.which == null &&
|
|
869
|
+
// `button` is an old DOM API.
|
|
870
|
+
// tslint:disable-next-line:no-any
|
|
871
|
+
e.button === 4) // middle click for IE
|
|
872
|
+
);
|
|
1222
873
|
}
|
|
1223
874
|
/**
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
875
|
+
* Determines and returns whether the given event (which is assumed
|
|
876
|
+
* to be a click event) is modified. A middle click is considered a modified
|
|
877
|
+
* click to retain the default browser action, which opens a link in a new tab.
|
|
878
|
+
* @param e The event.
|
|
879
|
+
* @return Whether the given event is modified.
|
|
1226
880
|
*/
|
|
1227
|
-
function
|
|
1228
|
-
|
|
1229
|
-
//
|
|
1230
|
-
//
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
881
|
+
function isModifiedClickEvent(e) {
|
|
882
|
+
return (
|
|
883
|
+
// `metaKey` is an old DOM API.
|
|
884
|
+
// tslint:disable-next-line:no-any
|
|
885
|
+
(isMac && e.metaKey) ||
|
|
886
|
+
// `ctrlKey` is an old DOM API.
|
|
887
|
+
// tslint:disable-next-line:no-any
|
|
888
|
+
(!isMac && e.ctrlKey) ||
|
|
889
|
+
isMiddleClick(e) ||
|
|
890
|
+
// `shiftKey` is an old DOM API.
|
|
891
|
+
// tslint:disable-next-line:no-any
|
|
892
|
+
e.shiftKey);
|
|
1234
893
|
}
|
|
894
|
+
/** Whether we are on WebKit (e.g., Chrome). */
|
|
895
|
+
const isWebKit = typeof navigator !== 'undefined' &&
|
|
896
|
+
!/Opera/.test(navigator.userAgent) &&
|
|
897
|
+
/WebKit/.test(navigator.userAgent);
|
|
898
|
+
/** Whether we are on IE. */
|
|
899
|
+
const isIe = typeof navigator !== 'undefined' &&
|
|
900
|
+
(/MSIE/.test(navigator.userAgent) || /Trident/.test(navigator.userAgent));
|
|
901
|
+
/** Whether we are on Gecko (e.g., Firefox). */
|
|
902
|
+
const isGecko = typeof navigator !== 'undefined' &&
|
|
903
|
+
!/Opera|WebKit/.test(navigator.userAgent) &&
|
|
904
|
+
/Gecko/.test(navigator.product);
|
|
1235
905
|
/**
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
906
|
+
* Determines and returns whether the given element is a valid target for
|
|
907
|
+
* keypress/keydown DOM events that act like regular DOM clicks.
|
|
908
|
+
* @param el The element.
|
|
909
|
+
* @return Whether the given element is a valid action key target.
|
|
1238
910
|
*/
|
|
1239
|
-
function
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
911
|
+
function isValidActionKeyTarget(el) {
|
|
912
|
+
if (!('getAttribute' in el)) {
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
if (isTextControl(el)) {
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
if (isNativelyActivatable(el)) {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
// `isContentEditable` is an old DOM API.
|
|
922
|
+
// tslint:disable-next-line:no-any
|
|
923
|
+
if (el.isContentEditable) {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
return true;
|
|
1243
927
|
}
|
|
1244
|
-
|
|
1245
928
|
/**
|
|
1246
|
-
* Whether
|
|
929
|
+
* Whether an event has a modifier key activated.
|
|
930
|
+
* @param e The event.
|
|
931
|
+
* @return True, if a modifier key is activated.
|
|
1247
932
|
*/
|
|
1248
|
-
|
|
933
|
+
function hasModifierKey(e) {
|
|
934
|
+
return (
|
|
935
|
+
// `ctrlKey` is an old DOM API.
|
|
936
|
+
// tslint:disable-next-line:no-any
|
|
937
|
+
e.ctrlKey ||
|
|
938
|
+
// `shiftKey` is an old DOM API.
|
|
939
|
+
// tslint:disable-next-line:no-any
|
|
940
|
+
e.shiftKey ||
|
|
941
|
+
// `altKey` is an old DOM API.
|
|
942
|
+
// tslint:disable-next-line:no-any
|
|
943
|
+
e.altKey ||
|
|
944
|
+
// `metaKey` is an old DOM API.
|
|
945
|
+
// tslint:disable-next-line:no-any
|
|
946
|
+
e.metaKey);
|
|
947
|
+
}
|
|
1249
948
|
/**
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
949
|
+
* Determines and returns whether the given event has a target that already
|
|
950
|
+
* has event handlers attached because it is a native HTML control. Used to
|
|
951
|
+
* determine if preventDefault should be called when isActionKeyEvent is true.
|
|
952
|
+
* @param e The event.
|
|
953
|
+
* @return If preventDefault should be called.
|
|
1253
954
|
*/
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
955
|
+
function shouldCallPreventDefaultOnNativeHtmlControl(e) {
|
|
956
|
+
const el = getTarget(e);
|
|
957
|
+
const tagName = el.tagName.toUpperCase();
|
|
958
|
+
const role = (el.getAttribute('role') || '').toUpperCase();
|
|
959
|
+
if (tagName === 'BUTTON' || role === 'BUTTON') {
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
962
|
+
if (!isNativeHTMLControl(el)) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
if (tagName === 'A') {
|
|
966
|
+
return false;
|
|
1266
967
|
}
|
|
1267
968
|
/**
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
969
|
+
* Fix for physical d-pads on feature phone platforms; the native event
|
|
970
|
+
* (ie. isTrusted: true) needs to fire to show the OPTION list. See
|
|
971
|
+
* b/135288469 for more info.
|
|
1271
972
|
*/
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
973
|
+
if (tagName === 'SELECT') {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
if (processSpace(el)) {
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
if (isTextControl(el)) {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Determines and returns whether the given event acts like a regular DOM click,
|
|
986
|
+
* and should be handled instead of the click. If this returns true, the caller
|
|
987
|
+
* will call preventDefault() to prevent a possible duplicate event.
|
|
988
|
+
* This is represented by a keypress (keydown on Gecko browsers) on Enter or
|
|
989
|
+
* Space key.
|
|
990
|
+
* @param e The event.
|
|
991
|
+
* @return True, if the event emulates a DOM click.
|
|
992
|
+
*/
|
|
993
|
+
function isActionKeyEvent(e) {
|
|
994
|
+
let key =
|
|
995
|
+
// `which` is an old DOM API.
|
|
996
|
+
// tslint:disable-next-line:no-any
|
|
997
|
+
e.which ||
|
|
998
|
+
// `keyCode` is an old DOM API.
|
|
999
|
+
// tslint:disable-next-line:no-any
|
|
1000
|
+
e.keyCode;
|
|
1001
|
+
if (!key && e.key) {
|
|
1002
|
+
key = ACTION_KEY_TO_KEYCODE[e.key];
|
|
1003
|
+
}
|
|
1004
|
+
if (isWebKit && key === KeyCode.MAC_ENTER) {
|
|
1005
|
+
key = KeyCode.ENTER;
|
|
1006
|
+
}
|
|
1007
|
+
if (key !== KeyCode.ENTER && key !== KeyCode.SPACE) {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
const el = getTarget(e);
|
|
1011
|
+
if (e.type !== EventType.KEYDOWN || !isValidActionKeyTarget(el) || hasModifierKey(e)) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
// For <input type="checkbox">, we must only handle the browser's native click
|
|
1015
|
+
// event, so that the browser can toggle the checkbox.
|
|
1016
|
+
if (processSpace(el) && key === KeyCode.SPACE) {
|
|
1017
|
+
return false;
|
|
1288
1018
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
removeEventListener(this.element, this.handlerInfos[i]);
|
|
1295
|
-
}
|
|
1296
|
-
this.handlerInfos = [];
|
|
1019
|
+
// If this element is non-focusable, ignore stray keystrokes (b/18337209)
|
|
1020
|
+
// Sscreen readers can move without tab focus, so any tabIndex is focusable.
|
|
1021
|
+
// See B/21809604
|
|
1022
|
+
if (!isFocusable(el)) {
|
|
1023
|
+
return false;
|
|
1297
1024
|
}
|
|
1025
|
+
const type = (el.getAttribute('role') ||
|
|
1026
|
+
el.type ||
|
|
1027
|
+
el.tagName).toUpperCase();
|
|
1028
|
+
const isSpecificTriggerKey = IDENTIFIER_TO_KEY_TRIGGER_MAPPING[type] % key === 0;
|
|
1029
|
+
const isDefaultTriggerKey = !(type in IDENTIFIER_TO_KEY_TRIGGER_MAPPING) && key === KeyCode.ENTER;
|
|
1030
|
+
const hasType = el.tagName.toUpperCase() !== 'INPUT' || !!el.type;
|
|
1031
|
+
return (isSpecificTriggerKey || isDefaultTriggerKey) && hasType;
|
|
1298
1032
|
}
|
|
1299
|
-
|
|
1300
1033
|
/**
|
|
1301
|
-
*
|
|
1302
|
-
* is
|
|
1034
|
+
* Checks whether a DOM element can receive keyboard focus.
|
|
1035
|
+
* This code is based on goog.dom.isFocusable, but simplified since we shouldn't
|
|
1036
|
+
* care about visibility if we're already handling a keyboard event.
|
|
1303
1037
|
*/
|
|
1304
|
-
function
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
}
|
|
1308
|
-
setA11yClickKey(eventInfo, true);
|
|
1309
|
-
// A 'click' triggered by a DOM keypress should be mapped to the 'click'
|
|
1310
|
-
// jsaction.
|
|
1311
|
-
setEventType(eventInfo, EventType.CLICK);
|
|
1038
|
+
function isFocusable(el) {
|
|
1039
|
+
return ((el.tagName in NATIVELY_FOCUSABLE_ELEMENTS || hasSpecifiedTabIndex(el)) &&
|
|
1040
|
+
!el.disabled);
|
|
1312
1041
|
}
|
|
1313
1042
|
/**
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1043
|
+
* @param element Element to check.
|
|
1044
|
+
* @return Whether the element has a specified tab index.
|
|
1316
1045
|
*/
|
|
1317
|
-
function
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1046
|
+
function hasSpecifiedTabIndex(element) {
|
|
1047
|
+
// IE returns 0 for an unset tabIndex, so we must use getAttributeNode(),
|
|
1048
|
+
// which returns an object with a 'specified' property if tabIndex is
|
|
1049
|
+
// specified. This works on other browsers, too.
|
|
1050
|
+
const attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
|
|
1051
|
+
return attrNode != null && attrNode.specified;
|
|
1052
|
+
}
|
|
1053
|
+
/** Element tagnames that are focusable by default. */
|
|
1054
|
+
const NATIVELY_FOCUSABLE_ELEMENTS = {
|
|
1055
|
+
'A': 1,
|
|
1056
|
+
'INPUT': 1,
|
|
1057
|
+
'TEXTAREA': 1,
|
|
1058
|
+
'SELECT': 1,
|
|
1059
|
+
'BUTTON': 1,
|
|
1060
|
+
};
|
|
1061
|
+
/** @return True, if the Space key was pressed. */
|
|
1062
|
+
function isSpaceKeyEvent(e) {
|
|
1063
|
+
const key =
|
|
1064
|
+
// `which` is an old DOM API.
|
|
1065
|
+
// tslint:disable-next-line:no-any
|
|
1066
|
+
e.which ||
|
|
1067
|
+
// `keyCode` is an old DOM API.
|
|
1068
|
+
// tslint:disable-next-line:no-any
|
|
1069
|
+
e.keyCode;
|
|
1070
|
+
const el = getTarget(e);
|
|
1071
|
+
const elementName = (el.type || el.tagName).toUpperCase();
|
|
1072
|
+
return key === KeyCode.SPACE && elementName !== 'CHECKBOX';
|
|
1324
1073
|
}
|
|
1325
1074
|
/**
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1075
|
+
* Determines whether the event corresponds to a non-bubbling mouse
|
|
1076
|
+
* event type (mouseenter, mouseleave, pointerenter, and pointerleave).
|
|
1077
|
+
*
|
|
1078
|
+
* During mouseover (mouseenter) and pointerover (pointerenter), the
|
|
1079
|
+
* relatedTarget is the element being entered from. During mouseout (mouseleave)
|
|
1080
|
+
* and pointerout (pointerleave), the relatedTarget is the element being exited
|
|
1081
|
+
* to.
|
|
1082
|
+
*
|
|
1083
|
+
* In both cases, if relatedTarget is outside target, then the corresponding
|
|
1084
|
+
* special event has occurred, otherwise it hasn't.
|
|
1085
|
+
*
|
|
1086
|
+
* @param e The mouseover/mouseout event.
|
|
1087
|
+
* @param type The type of the mouse special event.
|
|
1088
|
+
* @param element The element on which the jsaction for the
|
|
1089
|
+
* mouseenter/mouseleave event is defined.
|
|
1090
|
+
* @return True if the event is a mouseenter/mouseleave event.
|
|
1328
1091
|
*/
|
|
1329
|
-
function
|
|
1330
|
-
|
|
1331
|
-
//
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
setEventType(eventInfo, EventType.CLICKONLY);
|
|
1341
|
-
setAction(eventInfo, actionMap[EventType.CLICKONLY], actionElement);
|
|
1092
|
+
function isMouseSpecialEvent(e, type, element) {
|
|
1093
|
+
// `relatedTarget` is an old DOM API.
|
|
1094
|
+
// tslint:disable-next-line:no-any
|
|
1095
|
+
const related = e.relatedTarget;
|
|
1096
|
+
return (((e.type === EventType.MOUSEOVER && type === EventType.MOUSEENTER) ||
|
|
1097
|
+
(e.type === EventType.MOUSEOUT && type === EventType.MOUSELEAVE) ||
|
|
1098
|
+
(e.type === EventType.POINTEROVER && type === EventType.POINTERENTER) ||
|
|
1099
|
+
(e.type === EventType.POINTEROUT && type === EventType.POINTERLEAVE)) &&
|
|
1100
|
+
(!related || (related !== element && !contains(element, related))));
|
|
1342
1101
|
}
|
|
1343
|
-
|
|
1344
1102
|
/**
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
* name.
|
|
1353
|
-
*
|
|
1354
|
-
* See grammar in README.md for expected syntax in the attribute value.
|
|
1103
|
+
* Creates a new EventLike object for a mouseenter/mouseleave event that's
|
|
1104
|
+
* derived from the original corresponding mouseover/mouseout event.
|
|
1105
|
+
* @param e The event.
|
|
1106
|
+
* @param target The element on which the jsaction for the mouseenter/mouseleave
|
|
1107
|
+
* event is defined.
|
|
1108
|
+
* @return A modified event-like object copied from the event object passed into
|
|
1109
|
+
* this function.
|
|
1355
1110
|
*/
|
|
1356
|
-
|
|
1111
|
+
function createMouseSpecialEvent(e, target) {
|
|
1112
|
+
// We have to create a copy of the event object because we need to mutate
|
|
1113
|
+
// its fields. We do this for the special mouse events because the event
|
|
1114
|
+
// target needs to be retargeted to the action element rather than the real
|
|
1115
|
+
// element (since we are simulating the special mouse events with mouseover/
|
|
1116
|
+
// mouseout).
|
|
1117
|
+
//
|
|
1118
|
+
// Since we're making a copy anyways, we might as well attempt to convert
|
|
1119
|
+
// this event into a pseudo-real mouseenter/mouseleave event by adjusting
|
|
1120
|
+
// its type.
|
|
1121
|
+
//
|
|
1122
|
+
// tslint:disable-next-line:no-any
|
|
1123
|
+
const copy = {};
|
|
1124
|
+
for (const property in e) {
|
|
1125
|
+
if (property === 'srcElement' || property === 'target') {
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
const key = property;
|
|
1129
|
+
// Making a copy requires iterating through all properties of `Event`.
|
|
1130
|
+
// tslint:disable-next-line:no-dict-access-on-struct-type
|
|
1131
|
+
const value = e[key];
|
|
1132
|
+
if (typeof value === 'function') {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
// Value should be the expected type, but the value of `key` is not known
|
|
1136
|
+
// statically.
|
|
1137
|
+
// tslint:disable-next-line:no-any
|
|
1138
|
+
copy[key] = value;
|
|
1139
|
+
}
|
|
1140
|
+
if (e.type === EventType.MOUSEOVER) {
|
|
1141
|
+
copy['type'] = EventType.MOUSEENTER;
|
|
1142
|
+
}
|
|
1143
|
+
else if (e.type === EventType.MOUSEOUT) {
|
|
1144
|
+
copy['type'] = EventType.MOUSELEAVE;
|
|
1145
|
+
}
|
|
1146
|
+
else if (e.type === EventType.POINTEROVER) {
|
|
1147
|
+
copy['type'] = EventType.POINTERENTER;
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
copy['type'] = EventType.POINTERLEAVE;
|
|
1151
|
+
}
|
|
1152
|
+
copy['target'] = copy['srcElement'] = target;
|
|
1153
|
+
copy['bubbles'] = false;
|
|
1154
|
+
return copy;
|
|
1155
|
+
}
|
|
1357
1156
|
/**
|
|
1358
|
-
*
|
|
1359
|
-
* and
|
|
1360
|
-
*
|
|
1361
|
-
* impression logging and click tracking by the dot separated path
|
|
1362
|
-
* of all oi attributes in the chain of ancestors of the element.
|
|
1157
|
+
* Returns touch data extracted from the touch event: clientX, clientY, screenX
|
|
1158
|
+
* and screenY. If the event has no touch information at all, the returned
|
|
1159
|
+
* value is null.
|
|
1363
1160
|
*
|
|
1364
|
-
*
|
|
1161
|
+
* The fields of this Object are unquoted.
|
|
1162
|
+
*
|
|
1163
|
+
* @param event A touch event.
|
|
1365
1164
|
*/
|
|
1366
|
-
|
|
1165
|
+
function getTouchData(event) {
|
|
1166
|
+
const touch = (event.changedTouches && event.changedTouches[0]) || (event.touches && event.touches[0]);
|
|
1167
|
+
if (!touch) {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
clientX: touch.clientX,
|
|
1172
|
+
clientY: touch.clientY,
|
|
1173
|
+
screenX: touch.screenX,
|
|
1174
|
+
screenY: touch.screenY,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1367
1177
|
/**
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1178
|
+
* Creates a new EventLike object for a "click" event that's derived from the
|
|
1179
|
+
* original corresponding "touchend" event for a fast-click implementation.
|
|
1370
1180
|
*
|
|
1371
|
-
*
|
|
1181
|
+
* It takes a touch event, adds common fields found in a click event and
|
|
1182
|
+
* changes the type to 'click', so that the resulting event looks more like
|
|
1183
|
+
* a real click event.
|
|
1184
|
+
*
|
|
1185
|
+
* @param event A touch event.
|
|
1186
|
+
* @return A modified event-like object copied from the event object passed into
|
|
1187
|
+
* this function.
|
|
1372
1188
|
*/
|
|
1373
|
-
|
|
1189
|
+
function recreateTouchEventAsClick(event) {
|
|
1190
|
+
const click = {};
|
|
1191
|
+
click['originalEventType'] = event.type;
|
|
1192
|
+
click['type'] = EventType.CLICK;
|
|
1193
|
+
for (const property in event) {
|
|
1194
|
+
if (property === 'type' || property === 'srcElement') {
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
const key = property;
|
|
1198
|
+
// Making a copy requires iterating through all properties of `TouchEvent`.
|
|
1199
|
+
// tslint:disable-next-line:no-dict-access-on-struct-type
|
|
1200
|
+
const value = event[key];
|
|
1201
|
+
if (typeof value === 'function') {
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
// Value should be the expected type, but the value of `key` is not known
|
|
1205
|
+
// statically.
|
|
1206
|
+
// tslint:disable-next-line:no-any
|
|
1207
|
+
click[key] = value;
|
|
1208
|
+
}
|
|
1209
|
+
// Ensure that the event has the most recent timestamp. This timestamp
|
|
1210
|
+
// may be used in the future to validate or cancel subsequent click events.
|
|
1211
|
+
click['timeStamp'] = Date.now();
|
|
1212
|
+
// Emulate preventDefault and stopPropagation behavior
|
|
1213
|
+
click['defaultPrevented'] = false;
|
|
1214
|
+
click['preventDefault'] = syntheticPreventDefault;
|
|
1215
|
+
click['_propagationStopped'] = false;
|
|
1216
|
+
click['stopPropagation'] = syntheticStopPropagation;
|
|
1217
|
+
// Emulate click coordinates using touch info
|
|
1218
|
+
const touch = getTouchData(event);
|
|
1219
|
+
if (touch) {
|
|
1220
|
+
click['clientX'] = touch.clientX;
|
|
1221
|
+
click['clientY'] = touch.clientY;
|
|
1222
|
+
click['screenX'] = touch.screenX;
|
|
1223
|
+
click['screenY'] = touch.screenY;
|
|
1224
|
+
}
|
|
1225
|
+
return click;
|
|
1226
|
+
}
|
|
1374
1227
|
/**
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1228
|
+
* An implementation of "preventDefault" for a synthesized event. Simply
|
|
1229
|
+
* sets "defaultPrevented" property to true.
|
|
1377
1230
|
*/
|
|
1378
|
-
|
|
1231
|
+
function syntheticPreventDefault() {
|
|
1232
|
+
this.defaultPrevented = true;
|
|
1233
|
+
}
|
|
1379
1234
|
/**
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1382
|
-
* Used by ActionFlow.
|
|
1235
|
+
* An implementation of "stopPropagation" for a synthesized event. It simply
|
|
1236
|
+
* sets a synthetic non-standard "_propagationStopped" property to true.
|
|
1383
1237
|
*/
|
|
1384
|
-
|
|
1238
|
+
function syntheticStopPropagation() {
|
|
1239
|
+
this._propagationStopped = true;
|
|
1240
|
+
}
|
|
1385
1241
|
/**
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1388
|
-
* Impressions of jsactions on these elements are tracked too, if
|
|
1389
|
-
* requested by the impression() method of ActionFlow.
|
|
1390
|
-
*
|
|
1391
|
-
* Used by ActionFlow.
|
|
1242
|
+
* Mapping of KeyboardEvent.key values to
|
|
1243
|
+
* KeyCode values.
|
|
1392
1244
|
*/
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const Char = {
|
|
1397
|
-
/**
|
|
1398
|
-
* The separator between the namespace and the action name in the
|
|
1399
|
-
* jsaction attribute value.
|
|
1400
|
-
*/
|
|
1401
|
-
NAMESPACE_ACTION_SEPARATOR: '.',
|
|
1402
|
-
/**
|
|
1403
|
-
* The separator between the event name and action in the jsaction
|
|
1404
|
-
* attribute value.
|
|
1405
|
-
*/
|
|
1406
|
-
EVENT_ACTION_SEPARATOR: ':',
|
|
1407
|
-
/**
|
|
1408
|
-
* The separator between the logged oi attribute values in the &oi=
|
|
1409
|
-
* URL parameter value.
|
|
1410
|
-
*/
|
|
1411
|
-
OI_SEPARATOR: '.',
|
|
1412
|
-
/**
|
|
1413
|
-
* The separator between the key and the value pairs in the &cad=
|
|
1414
|
-
* URL parameter value.
|
|
1415
|
-
*/
|
|
1416
|
-
CAD_KEY_VALUE_SEPARATOR: ':',
|
|
1417
|
-
/**
|
|
1418
|
-
* The separator between the key-value pairs in the &cad= URL
|
|
1419
|
-
* parameter value.
|
|
1420
|
-
*/
|
|
1421
|
-
CAD_SEPARATOR: ',',
|
|
1245
|
+
const ACTION_KEY_TO_KEYCODE = {
|
|
1246
|
+
'Enter': KeyCode.ENTER,
|
|
1247
|
+
' ': KeyCode.SPACE,
|
|
1422
1248
|
};
|
|
1423
|
-
|
|
1424
|
-
/**
|
|
1425
|
-
* The parsed value of the jsaction attribute is stored in this
|
|
1426
|
-
* property on the DOM node. The parsed value is an Object. The
|
|
1427
|
-
* property names of the object are the events; the values are the
|
|
1428
|
-
* names of the actions. This property is attached even on nodes
|
|
1429
|
-
* that don't have a jsaction attribute as an optimization, because
|
|
1430
|
-
* property lookup is faster than attribute access.
|
|
1431
|
-
*/
|
|
1432
|
-
const JSACTION = '__jsaction';
|
|
1433
|
-
/** The value of the oi attribute as a property, for faster access. */
|
|
1434
|
-
const OI = '__oi';
|
|
1435
1249
|
/**
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
* over a document where the DOM structure can't reflect that structure.
|
|
1250
|
+
* Mapping of HTML element identifiers (ARIA role, type, or tagName) to the
|
|
1251
|
+
* keys (enter and/or space) that should activate them. A value of zero means
|
|
1252
|
+
* that both should activate them.
|
|
1440
1253
|
*/
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1254
|
+
const IDENTIFIER_TO_KEY_TRIGGER_MAPPING = {
|
|
1255
|
+
'A': KeyCode.ENTER,
|
|
1256
|
+
'BUTTON': 0,
|
|
1257
|
+
'CHECKBOX': KeyCode.SPACE,
|
|
1258
|
+
'COMBOBOX': KeyCode.ENTER,
|
|
1259
|
+
'FILE': 0,
|
|
1260
|
+
'GRIDCELL': KeyCode.ENTER,
|
|
1261
|
+
'LINK': KeyCode.ENTER,
|
|
1262
|
+
'LISTBOX': KeyCode.ENTER,
|
|
1263
|
+
'MENU': 0,
|
|
1264
|
+
'MENUBAR': 0,
|
|
1265
|
+
'MENUITEM': 0,
|
|
1266
|
+
'MENUITEMCHECKBOX': 0,
|
|
1267
|
+
'MENUITEMRADIO': 0,
|
|
1268
|
+
'OPTION': 0,
|
|
1269
|
+
'RADIO': KeyCode.SPACE,
|
|
1270
|
+
'RADIOGROUP': KeyCode.SPACE,
|
|
1271
|
+
'RESET': 0,
|
|
1272
|
+
'SUBMIT': 0,
|
|
1273
|
+
'SWITCH': KeyCode.SPACE,
|
|
1274
|
+
'TAB': 0,
|
|
1275
|
+
'TREE': KeyCode.ENTER,
|
|
1276
|
+
'TREEITEM': KeyCode.ENTER,
|
|
1447
1277
|
};
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* Map from jsaction annotation to a parsed map from event name to action name.
|
|
1451
|
-
*/
|
|
1452
|
-
const parseCache = {};
|
|
1453
1278
|
/**
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
* @param element .
|
|
1457
|
-
* @return
|
|
1279
|
+
* Returns whether or not to process space based on the type of the element;
|
|
1280
|
+
* checks to make sure that type is not null.
|
|
1281
|
+
* @param element The element.
|
|
1282
|
+
* @return Whether or not to process space based on type.
|
|
1458
1283
|
*/
|
|
1459
|
-
function
|
|
1460
|
-
|
|
1461
|
-
return
|
|
1284
|
+
function processSpace(element) {
|
|
1285
|
+
const type = (element.getAttribute('type') || element.tagName).toUpperCase();
|
|
1286
|
+
return type in PROCESS_SPACE;
|
|
1462
1287
|
}
|
|
1463
1288
|
/**
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
* @
|
|
1467
|
-
* @param actionMap Map from event to qualified name of the jsaction bound to
|
|
1468
|
-
* it.
|
|
1289
|
+
* Returns whether or not the given element is a text control.
|
|
1290
|
+
* @param el The element.
|
|
1291
|
+
* @return Whether or not the given element is a text control.
|
|
1469
1292
|
*/
|
|
1470
|
-
function
|
|
1471
|
-
|
|
1472
|
-
|
|
1293
|
+
function isTextControl(el) {
|
|
1294
|
+
const type = (el.getAttribute('type') || el.tagName).toUpperCase();
|
|
1295
|
+
return type in TEXT_CONTROLS;
|
|
1473
1296
|
}
|
|
1474
1297
|
/**
|
|
1475
|
-
*
|
|
1476
|
-
*
|
|
1477
|
-
* @
|
|
1478
|
-
* @return Parsed jsaction attribute value, if already present in the cache.
|
|
1298
|
+
* Returns if the given element is a native HTML control.
|
|
1299
|
+
* @param el The element.
|
|
1300
|
+
* @return If the given element is a native HTML control.
|
|
1479
1301
|
*/
|
|
1480
|
-
function
|
|
1481
|
-
return
|
|
1302
|
+
function isNativeHTMLControl(el) {
|
|
1303
|
+
return el.tagName.toUpperCase() in NATIVE_HTML_CONTROLS;
|
|
1482
1304
|
}
|
|
1483
1305
|
/**
|
|
1484
|
-
*
|
|
1485
|
-
*
|
|
1486
|
-
*
|
|
1487
|
-
* @param
|
|
1306
|
+
* Returns if the given element is natively activatable. Browsers emit click
|
|
1307
|
+
* events for natively activatable elements, even when activated via keyboard.
|
|
1308
|
+
* For these elements, we don't need to raise a11y click events.
|
|
1309
|
+
* @param el The element.
|
|
1310
|
+
* @return If the given element is a native HTML control.
|
|
1488
1311
|
*/
|
|
1489
|
-
function
|
|
1490
|
-
|
|
1312
|
+
function isNativelyActivatable(el) {
|
|
1313
|
+
return (el.tagName.toUpperCase() === 'BUTTON' ||
|
|
1314
|
+
(!!el.type && el.type.toUpperCase() === 'FILE'));
|
|
1491
1315
|
}
|
|
1492
1316
|
/**
|
|
1493
|
-
*
|
|
1494
|
-
*
|
|
1495
|
-
*
|
|
1317
|
+
* HTML <input> types (not ARIA roles) which will auto-trigger a click event for
|
|
1318
|
+
* the Space key, with side-effects. We will not call preventDefault if space is
|
|
1319
|
+
* pressed, nor will we raise a11y click events. For all other elements, we can
|
|
1320
|
+
* suppress the default event (which has no desired side-effects) and handle the
|
|
1321
|
+
* keydown ourselves.
|
|
1496
1322
|
*/
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1323
|
+
const PROCESS_SPACE = {
|
|
1324
|
+
'CHECKBOX': true,
|
|
1325
|
+
'FILE': true,
|
|
1326
|
+
'OPTION': true,
|
|
1327
|
+
'RADIO': true,
|
|
1328
|
+
};
|
|
1329
|
+
/** TagNames and Input types for which to not process enter/space as click. */
|
|
1330
|
+
const TEXT_CONTROLS = {
|
|
1331
|
+
'COLOR': true,
|
|
1332
|
+
'DATE': true,
|
|
1333
|
+
'DATETIME': true,
|
|
1334
|
+
'DATETIME-LOCAL': true,
|
|
1335
|
+
'EMAIL': true,
|
|
1336
|
+
'MONTH': true,
|
|
1337
|
+
'NUMBER': true,
|
|
1338
|
+
'PASSWORD': true,
|
|
1339
|
+
'RANGE': true,
|
|
1340
|
+
'SEARCH': true,
|
|
1341
|
+
'TEL': true,
|
|
1342
|
+
'TEXT': true,
|
|
1343
|
+
'TEXTAREA': true,
|
|
1344
|
+
'TIME': true,
|
|
1345
|
+
'URL': true,
|
|
1346
|
+
'WEEK': true,
|
|
1347
|
+
};
|
|
1348
|
+
/** TagNames that are native HTML controls. */
|
|
1349
|
+
const NATIVE_HTML_CONTROLS = {
|
|
1350
|
+
'A': true,
|
|
1351
|
+
'AREA': true,
|
|
1352
|
+
'BUTTON': true,
|
|
1353
|
+
'DIALOG': true,
|
|
1354
|
+
'IMG': true,
|
|
1355
|
+
'INPUT': true,
|
|
1356
|
+
'LINK': true,
|
|
1357
|
+
'MENU': true,
|
|
1358
|
+
'OPTGROUP': true,
|
|
1359
|
+
'OPTION': true,
|
|
1360
|
+
'PROGRESS': true,
|
|
1361
|
+
'SELECT': true,
|
|
1362
|
+
'TEXTAREA': true,
|
|
1363
|
+
};
|
|
1364
|
+
/** Exported for testing. */
|
|
1365
|
+
const testing = {
|
|
1366
|
+
setIsMac(value) {
|
|
1367
|
+
isMac = value;
|
|
1368
|
+
},
|
|
1369
|
+
};
|
|
1502
1370
|
|
|
1503
1371
|
/**
|
|
1504
1372
|
* Since maps from event to action are immutable we can use a single map
|
|
@@ -1520,26 +1388,7 @@ class ActionResolver {
|
|
|
1520
1388
|
this.populateClickOnlyAction = undefined;
|
|
1521
1389
|
this.syntheticMouseEventSupport = syntheticMouseEventSupport;
|
|
1522
1390
|
}
|
|
1523
|
-
|
|
1524
|
-
if (getResolved(eventInfo)) {
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
this.populateAction(eventInfo);
|
|
1528
|
-
setResolved(eventInfo, true);
|
|
1529
|
-
}
|
|
1530
|
-
/**
|
|
1531
|
-
* Searches for a jsaction that the DOM event maps to and creates an
|
|
1532
|
-
* object containing event information used for dispatching by
|
|
1533
|
-
* jsaction.Dispatcher. This method populates the `action` and `actionElement`
|
|
1534
|
-
* fields of the EventInfo object passed in by finding the first
|
|
1535
|
-
* jsaction attribute above the target Node of the event, and below
|
|
1536
|
-
* the container Node, that specifies a jsaction for the event
|
|
1537
|
-
* type. If no such jsaction is found, then action is undefined.
|
|
1538
|
-
*
|
|
1539
|
-
* @param eventInfo `EventInfo` to set `action` and `actionElement` if an
|
|
1540
|
-
* action is found on any `Element` in the path of the `Event`.
|
|
1541
|
-
*/
|
|
1542
|
-
populateAction(eventInfo) {
|
|
1391
|
+
resolveEventType(eventInfo) {
|
|
1543
1392
|
// We distinguish modified and plain clicks in order to support the
|
|
1544
1393
|
// default browser behavior of modified clicks on links; usually to
|
|
1545
1394
|
// open the URL of the link in new tab or new window on ctrl/cmd
|
|
@@ -1580,10 +1429,38 @@ class ActionResolver {
|
|
|
1580
1429
|
else if (this.a11yClickSupport) {
|
|
1581
1430
|
this.updateEventInfoForA11yClick(eventInfo);
|
|
1582
1431
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1432
|
+
}
|
|
1433
|
+
resolveAction(eventInfo) {
|
|
1434
|
+
if (getResolved(eventInfo)) {
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
this.populateAction(eventInfo, getTargetElement(eventInfo));
|
|
1438
|
+
setResolved(eventInfo, true);
|
|
1439
|
+
}
|
|
1440
|
+
resolveParentAction(eventInfo) {
|
|
1441
|
+
const action = getAction(eventInfo);
|
|
1442
|
+
const actionElement = action && getActionElement(action);
|
|
1443
|
+
unsetAction(eventInfo);
|
|
1444
|
+
const parentNode = actionElement && this.getParentNode(actionElement);
|
|
1445
|
+
if (!parentNode) {
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
this.populateAction(eventInfo, parentNode);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Searches for a jsaction that the DOM event maps to and creates an
|
|
1452
|
+
* object containing event information used for dispatching by
|
|
1453
|
+
* jsaction.Dispatcher. This method populates the `action` and `actionElement`
|
|
1454
|
+
* fields of the EventInfo object passed in by finding the first
|
|
1455
|
+
* jsaction attribute above the target Node of the event, and below
|
|
1456
|
+
* the container Node, that specifies a jsaction for the event
|
|
1457
|
+
* type. If no such jsaction is found, then action is undefined.
|
|
1458
|
+
*
|
|
1459
|
+
* @param eventInfo `EventInfo` to set `action` and `actionElement` if an
|
|
1460
|
+
* action is found on any `Element` in the path of the `Event`.
|
|
1461
|
+
*/
|
|
1462
|
+
populateAction(eventInfo, currentTarget) {
|
|
1463
|
+
let actionElement = currentTarget;
|
|
1587
1464
|
while (actionElement && actionElement !== getContainer(eventInfo)) {
|
|
1588
1465
|
if (actionElement.nodeType === Node.ELEMENT_NODE) {
|
|
1589
1466
|
this.populateActionOnElement(actionElement, eventInfo);
|
|
@@ -1594,16 +1471,7 @@ class ActionResolver {
|
|
|
1594
1471
|
// ancestor chain of the event target node.
|
|
1595
1472
|
break;
|
|
1596
1473
|
}
|
|
1597
|
-
|
|
1598
|
-
actionElement = actionElement[OWNER];
|
|
1599
|
-
continue;
|
|
1600
|
-
}
|
|
1601
|
-
if (actionElement.parentNode?.nodeName !== '#document-fragment') {
|
|
1602
|
-
actionElement = actionElement.parentNode;
|
|
1603
|
-
}
|
|
1604
|
-
else {
|
|
1605
|
-
actionElement = actionElement.parentNode?.host ?? null;
|
|
1606
|
-
}
|
|
1474
|
+
actionElement = this.getParentNode(actionElement);
|
|
1607
1475
|
}
|
|
1608
1476
|
const action = getAction(eventInfo);
|
|
1609
1477
|
if (!action) {
|
|
@@ -1643,6 +1511,22 @@ class ActionResolver {
|
|
|
1643
1511
|
}
|
|
1644
1512
|
}
|
|
1645
1513
|
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Walk to the parent node, unless the node has a different owner in
|
|
1516
|
+
* which case we walk to the owner. Attempt to walk to host of a
|
|
1517
|
+
* shadow root if needed.
|
|
1518
|
+
*/
|
|
1519
|
+
getParentNode(element) {
|
|
1520
|
+
const owner = element[OWNER];
|
|
1521
|
+
if (owner) {
|
|
1522
|
+
return owner;
|
|
1523
|
+
}
|
|
1524
|
+
const parentNode = element.parentNode;
|
|
1525
|
+
if (parentNode?.nodeName === '#document-fragment') {
|
|
1526
|
+
return parentNode?.host ?? null;
|
|
1527
|
+
}
|
|
1528
|
+
return parentNode;
|
|
1529
|
+
}
|
|
1646
1530
|
/**
|
|
1647
1531
|
* Accesses the jsaction map on a node and retrieves the name of the
|
|
1648
1532
|
* action the given event is mapped to, if any. It parses the
|
|
@@ -1711,6 +1595,339 @@ class ActionResolver {
|
|
|
1711
1595
|
}
|
|
1712
1596
|
}
|
|
1713
1597
|
|
|
1598
|
+
/**
|
|
1599
|
+
* @fileoverview An enum to control who can call certain jsaction APIs.
|
|
1600
|
+
*/
|
|
1601
|
+
var Restriction;
|
|
1602
|
+
(function (Restriction) {
|
|
1603
|
+
Restriction[Restriction["I_AM_THE_JSACTION_FRAMEWORK"] = 0] = "I_AM_THE_JSACTION_FRAMEWORK";
|
|
1604
|
+
})(Restriction || (Restriction = {}));
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* Receives a DOM event, determines the jsaction associated with the source
|
|
1608
|
+
* element of the DOM event, and invokes the handler associated with the
|
|
1609
|
+
* jsaction.
|
|
1610
|
+
*/
|
|
1611
|
+
class Dispatcher {
|
|
1612
|
+
/**
|
|
1613
|
+
* Options are:
|
|
1614
|
+
* - `eventReplayer`: When the event contract dispatches replay events
|
|
1615
|
+
* to the Dispatcher, the Dispatcher collects them and in the next tick
|
|
1616
|
+
* dispatches them to the `eventReplayer`. Defaults to dispatching to `dispatchDelegate`.
|
|
1617
|
+
* @param dispatchDelegate A function that should handle dispatching an `EventInfoWrapper` to handlers.
|
|
1618
|
+
*/
|
|
1619
|
+
constructor(dispatchDelegate, { actionResolver, eventReplayer, } = {}) {
|
|
1620
|
+
this.dispatchDelegate = dispatchDelegate;
|
|
1621
|
+
/** Whether the event replay is scheduled. */
|
|
1622
|
+
this.eventReplayScheduled = false;
|
|
1623
|
+
/** The queue of events. */
|
|
1624
|
+
this.replayEventInfoWrappers = [];
|
|
1625
|
+
this.actionResolver = actionResolver;
|
|
1626
|
+
this.eventReplayer = eventReplayer;
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Receives an event or the event queue from the EventContract. The event
|
|
1630
|
+
* queue is copied and it attempts to replay.
|
|
1631
|
+
* If event info is passed in it looks for an action handler that can handle
|
|
1632
|
+
* the given event. If there is no handler registered queues the event and
|
|
1633
|
+
* checks if a loader is registered for the given namespace. If so, calls it.
|
|
1634
|
+
*
|
|
1635
|
+
* Alternatively, if in global dispatch mode, calls all registered global
|
|
1636
|
+
* handlers for the appropriate event type.
|
|
1637
|
+
*
|
|
1638
|
+
* The three functionalities of this call are deliberately not split into
|
|
1639
|
+
* three methods (and then declared as an abstract interface), because the
|
|
1640
|
+
* interface is used by EventContract, which lives in a different jsbinary.
|
|
1641
|
+
* Therefore the interface between the three is defined entirely in terms that
|
|
1642
|
+
* are invariant under jscompiler processing (Function and Array, as opposed
|
|
1643
|
+
* to a custom type with method names).
|
|
1644
|
+
*
|
|
1645
|
+
* @param eventInfo The info for the event that triggered this call or the
|
|
1646
|
+
* queue of events from EventContract.
|
|
1647
|
+
*/
|
|
1648
|
+
dispatch(eventInfo) {
|
|
1649
|
+
const eventInfoWrapper = new EventInfoWrapper(eventInfo);
|
|
1650
|
+
this.actionResolver?.resolveEventType(eventInfo);
|
|
1651
|
+
this.actionResolver?.resolveAction(eventInfo);
|
|
1652
|
+
const action = eventInfoWrapper.getAction();
|
|
1653
|
+
if (action && shouldPreventDefaultBeforeDispatching(action.element, eventInfoWrapper)) {
|
|
1654
|
+
preventDefault(eventInfoWrapper.getEvent());
|
|
1655
|
+
}
|
|
1656
|
+
if (this.eventReplayer && eventInfoWrapper.getIsReplay()) {
|
|
1657
|
+
this.scheduleEventInfoWrapperReplay(eventInfoWrapper);
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
this.dispatchDelegate(eventInfoWrapper);
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Schedules an `EventInfoWrapper` for replay. The replaying will happen in its own
|
|
1664
|
+
* stack once the current flow cedes control. This is done to mimic
|
|
1665
|
+
* browser event handling.
|
|
1666
|
+
*/
|
|
1667
|
+
scheduleEventInfoWrapperReplay(eventInfoWrapper) {
|
|
1668
|
+
this.replayEventInfoWrappers.push(eventInfoWrapper);
|
|
1669
|
+
if (this.eventReplayScheduled) {
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
this.eventReplayScheduled = true;
|
|
1673
|
+
Promise.resolve().then(() => {
|
|
1674
|
+
this.eventReplayScheduled = false;
|
|
1675
|
+
this.eventReplayer(this.replayEventInfoWrappers);
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Creates an `EventReplayer` that calls the `replay` function for every `eventInfoWrapper` in
|
|
1681
|
+
* the queue.
|
|
1682
|
+
*/
|
|
1683
|
+
function createEventReplayer(replay) {
|
|
1684
|
+
return (eventInfoWrappers) => {
|
|
1685
|
+
for (const eventInfoWrapper of eventInfoWrappers) {
|
|
1686
|
+
replay(eventInfoWrapper);
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Returns true if the default action of this event should be prevented before
|
|
1692
|
+
* this event is dispatched.
|
|
1693
|
+
*/
|
|
1694
|
+
function shouldPreventDefaultBeforeDispatching(actionElement, eventInfoWrapper) {
|
|
1695
|
+
// Prevent browser from following <a> node links if a jsaction is present
|
|
1696
|
+
// and we are dispatching the action now. Note that the targetElement may be
|
|
1697
|
+
// a child of an anchor that has a jsaction attached. For that reason, we
|
|
1698
|
+
// need to check the actionElement rather than the targetElement.
|
|
1699
|
+
return ((actionElement.tagName === 'A' && eventInfoWrapper.getEventType() === EventType.CLICK) ||
|
|
1700
|
+
eventInfoWrapper.getEventType() === EventType.CLICKMOD);
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Registers deferred functionality for an EventContract and a Jsaction
|
|
1704
|
+
* Dispatcher.
|
|
1705
|
+
*/
|
|
1706
|
+
function registerDispatcher$1(eventContract, dispatcher) {
|
|
1707
|
+
eventContract.ecrd((eventInfo) => {
|
|
1708
|
+
dispatcher.dispatch(eventInfo);
|
|
1709
|
+
}, Restriction.I_AM_THE_JSACTION_FRAMEWORK);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
/** An internal symbol used to indicate whether propagation should be stopped or not. */
|
|
1713
|
+
const PROPAGATION_STOPPED_SYMBOL = Symbol.for('propagationStopped');
|
|
1714
|
+
/** Extra event phases beyond what the browser provides. */
|
|
1715
|
+
const EventPhase = {
|
|
1716
|
+
REPLAY: 101,
|
|
1717
|
+
};
|
|
1718
|
+
const PREVENT_DEFAULT_ERROR_MESSAGE_DETAILS = ' Because event replay occurs after browser dispatch, `preventDefault` would have no ' +
|
|
1719
|
+
'effect. You can check whether an event is being replayed by accessing the event phase: ' +
|
|
1720
|
+
'`event.eventPhase === EventPhase.REPLAY`.';
|
|
1721
|
+
const PREVENT_DEFAULT_ERROR_MESSAGE = `\`preventDefault\` called during event replay.`;
|
|
1722
|
+
const COMPOSED_PATH_ERROR_MESSAGE_DETAILS = () => ngDevMode
|
|
1723
|
+
? ' Because event replay occurs after browser ' +
|
|
1724
|
+
'dispatch, `composedPath()` will be empty. Iterate parent nodes from `event.target` or ' +
|
|
1725
|
+
'`event.currentTarget` if you need to check elements in the event path.'
|
|
1726
|
+
: '';
|
|
1727
|
+
const COMPOSED_PATH_ERROR_MESSAGE = `\`composedPath\` called during event replay.`;
|
|
1728
|
+
/**
|
|
1729
|
+
* A dispatcher that uses browser-based `Event` semantics, for example bubbling, `stopPropagation`,
|
|
1730
|
+
* `currentTarget`, etc.
|
|
1731
|
+
*/
|
|
1732
|
+
class EventDispatcher {
|
|
1733
|
+
constructor(dispatchDelegate) {
|
|
1734
|
+
this.dispatchDelegate = dispatchDelegate;
|
|
1735
|
+
this.actionResolver = new ActionResolver();
|
|
1736
|
+
this.dispatcher = new Dispatcher((eventInfoWrapper) => {
|
|
1737
|
+
this.dispatchToDelegate(eventInfoWrapper);
|
|
1738
|
+
}, {
|
|
1739
|
+
actionResolver: this.actionResolver,
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* The entrypoint for the `EventContract` dispatch.
|
|
1744
|
+
*/
|
|
1745
|
+
dispatch(eventInfo) {
|
|
1746
|
+
this.dispatcher.dispatch(eventInfo);
|
|
1747
|
+
}
|
|
1748
|
+
/** Internal method that does basic disaptching. */
|
|
1749
|
+
dispatchToDelegate(eventInfoWrapper) {
|
|
1750
|
+
if (eventInfoWrapper.getIsReplay()) {
|
|
1751
|
+
prepareEventForReplay(eventInfoWrapper);
|
|
1752
|
+
}
|
|
1753
|
+
prepareEventForBubbling(eventInfoWrapper);
|
|
1754
|
+
while (eventInfoWrapper.getAction()) {
|
|
1755
|
+
prepareEventForDispatch(eventInfoWrapper);
|
|
1756
|
+
this.dispatchDelegate(eventInfoWrapper.getEvent(), eventInfoWrapper.getAction().name);
|
|
1757
|
+
if (propagationStopped(eventInfoWrapper)) {
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
this.actionResolver.resolveParentAction(eventInfoWrapper.eventInfo);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function prepareEventForBubbling(eventInfoWrapper) {
|
|
1765
|
+
const event = eventInfoWrapper.getEvent();
|
|
1766
|
+
const stopPropagation = () => {
|
|
1767
|
+
event[PROPAGATION_STOPPED_SYMBOL] = true;
|
|
1768
|
+
};
|
|
1769
|
+
patchEventInstance(event, 'stopPropagation', stopPropagation);
|
|
1770
|
+
patchEventInstance(event, 'stopImmediatePropagation', stopPropagation);
|
|
1771
|
+
}
|
|
1772
|
+
function propagationStopped(eventInfoWrapper) {
|
|
1773
|
+
const event = eventInfoWrapper.getEvent();
|
|
1774
|
+
return !!event[PROPAGATION_STOPPED_SYMBOL];
|
|
1775
|
+
}
|
|
1776
|
+
function prepareEventForReplay(eventInfoWrapper) {
|
|
1777
|
+
const event = eventInfoWrapper.getEvent();
|
|
1778
|
+
const target = eventInfoWrapper.getTargetElement();
|
|
1779
|
+
patchEventInstance(event, 'target', target);
|
|
1780
|
+
patchEventInstance(event, 'eventPhase', EventPhase.REPLAY);
|
|
1781
|
+
patchEventInstance(event, 'preventDefault', () => {
|
|
1782
|
+
throw new Error(PREVENT_DEFAULT_ERROR_MESSAGE + (ngDevMode ? PREVENT_DEFAULT_ERROR_MESSAGE_DETAILS : ''));
|
|
1783
|
+
});
|
|
1784
|
+
patchEventInstance(event, 'composedPath', () => {
|
|
1785
|
+
throw new Error(COMPOSED_PATH_ERROR_MESSAGE + (ngDevMode ? COMPOSED_PATH_ERROR_MESSAGE_DETAILS : ''));
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
function prepareEventForDispatch(eventInfoWrapper) {
|
|
1789
|
+
const event = eventInfoWrapper.getEvent();
|
|
1790
|
+
const currentTarget = eventInfoWrapper.getAction()?.element;
|
|
1791
|
+
if (currentTarget) {
|
|
1792
|
+
patchEventInstance(event, 'currentTarget', currentTarget, {
|
|
1793
|
+
// `currentTarget` is going to get reassigned every dispatch.
|
|
1794
|
+
configurable: true,
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Patch `Event` instance during non-standard `Event` dispatch. This patches just the `Event`
|
|
1800
|
+
* instance that the browser created, it does not patch global properties or methods.
|
|
1801
|
+
*
|
|
1802
|
+
* This is necessary because dispatching an `Event` outside of browser dispatch results in
|
|
1803
|
+
* incorrect properties and methods that need to be polyfilled or do not work.
|
|
1804
|
+
*
|
|
1805
|
+
* JSAction dispatch adds two extra "phases" to event dispatch:
|
|
1806
|
+
* 1. Event delegation - the event is being dispatched by a delegating event handler on a container
|
|
1807
|
+
* (typically `window.document.documentElement`), to a delegated event handler on some child
|
|
1808
|
+
* element. Certain `Event` properties will be unintuitive, such as `currentTarget`, which would
|
|
1809
|
+
* be the container rather than the child element. Bubbling would also not work. In order to
|
|
1810
|
+
* emulate the browser, these properties and methods on the `Event` are patched.
|
|
1811
|
+
* 2. Event replay - the event is being dispatched by the framework once the handlers have been
|
|
1812
|
+
* loaded (during hydration, or late-loaded). Certain `Event` properties can be unset by the
|
|
1813
|
+
* browser because the `Event` is no longer actively being dispatched, such as `target`. Other
|
|
1814
|
+
* methods have no effect because the `Event` has already been dispatched, such as
|
|
1815
|
+
* `preventDefault`. Bubbling would also not work. These properties and methods are patched,
|
|
1816
|
+
* either to fill in information that the browser may have removed, or to throw errors in methods
|
|
1817
|
+
* that no longer behave as expected.
|
|
1818
|
+
*/
|
|
1819
|
+
function patchEventInstance(event, property, value, { configurable = false } = {}) {
|
|
1820
|
+
Object.defineProperty(event, property, { value, configurable });
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Registers deferred functionality for an EventContract and a Jsaction
|
|
1824
|
+
* Dispatcher.
|
|
1825
|
+
*/
|
|
1826
|
+
function registerDispatcher(eventContract, dispatcher) {
|
|
1827
|
+
eventContract.ecrd((eventInfo) => {
|
|
1828
|
+
dispatcher.dispatch(eventInfo);
|
|
1829
|
+
}, Restriction.I_AM_THE_JSACTION_FRAMEWORK);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* Whether the user agent is running on iOS.
|
|
1834
|
+
*/
|
|
1835
|
+
const isIos = typeof navigator !== 'undefined' && /iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
1836
|
+
/**
|
|
1837
|
+
* A class representing a container node and all the event handlers
|
|
1838
|
+
* installed on it. Used so that handlers can be cleaned up if the
|
|
1839
|
+
* container is removed from the contract.
|
|
1840
|
+
*/
|
|
1841
|
+
class EventContractContainer {
|
|
1842
|
+
/**
|
|
1843
|
+
* @param element The container Element.
|
|
1844
|
+
*/
|
|
1845
|
+
constructor(element) {
|
|
1846
|
+
this.element = element;
|
|
1847
|
+
/**
|
|
1848
|
+
* Array of event handlers and their corresponding event types that are
|
|
1849
|
+
* installed on this container.
|
|
1850
|
+
*
|
|
1851
|
+
*/
|
|
1852
|
+
this.handlerInfos = [];
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Installs the provided installer on the element owned by this container,
|
|
1856
|
+
* and maintains a reference to resulting handler in order to remove it
|
|
1857
|
+
* later if desired.
|
|
1858
|
+
*/
|
|
1859
|
+
addEventListener(eventType, getHandler) {
|
|
1860
|
+
// In iOS, event bubbling doesn't happen automatically in any DOM element,
|
|
1861
|
+
// unless it has an onclick attribute or DOM event handler attached to it.
|
|
1862
|
+
// This breaks JsAction in some cases. See "Making Elements Clickable"
|
|
1863
|
+
// section at http://goo.gl/2VoGnB.
|
|
1864
|
+
//
|
|
1865
|
+
// A workaround for this issue is to change the CSS cursor style to 'pointer'
|
|
1866
|
+
// for the container element, which magically turns on event bubbling. This
|
|
1867
|
+
// solution is described in the comments section at http://goo.gl/6pEO1z.
|
|
1868
|
+
//
|
|
1869
|
+
// We use a navigator.userAgent check here as this problem is present both
|
|
1870
|
+
// on Mobile Safari and thin WebKit wrappers, such as Chrome for iOS.
|
|
1871
|
+
if (isIos) {
|
|
1872
|
+
this.element.style.cursor = 'pointer';
|
|
1873
|
+
}
|
|
1874
|
+
this.handlerInfos.push(addEventListener(this.element, eventType, getHandler(this.element)));
|
|
1875
|
+
}
|
|
1876
|
+
/**
|
|
1877
|
+
* Removes all the handlers installed on this container.
|
|
1878
|
+
*/
|
|
1879
|
+
cleanUp() {
|
|
1880
|
+
for (let i = 0; i < this.handlerInfos.length; i++) {
|
|
1881
|
+
removeEventListener(this.element, this.handlerInfos[i]);
|
|
1882
|
+
}
|
|
1883
|
+
this.handlerInfos = [];
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/**
|
|
1888
|
+
* Update `EventInfo` to be `eventType = 'click'` and sets `a11yClickKey` if it
|
|
1889
|
+
* is a a11y click.
|
|
1890
|
+
*/
|
|
1891
|
+
function updateEventInfoForA11yClick(eventInfo) {
|
|
1892
|
+
if (!isActionKeyEvent(getEvent(eventInfo))) {
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
setA11yClickKey(eventInfo, true);
|
|
1896
|
+
// A 'click' triggered by a DOM keypress should be mapped to the 'click'
|
|
1897
|
+
// jsaction.
|
|
1898
|
+
setEventType(eventInfo, EventType.CLICK);
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Call `preventDefault` on an a11y click if it is space key or to prevent the
|
|
1902
|
+
* browser's default action for native HTML controls.
|
|
1903
|
+
*/
|
|
1904
|
+
function preventDefaultForA11yClick(eventInfo) {
|
|
1905
|
+
if (!getA11yClickKey(eventInfo) ||
|
|
1906
|
+
(!isSpaceKeyEvent(getEvent(eventInfo)) &&
|
|
1907
|
+
!shouldCallPreventDefaultOnNativeHtmlControl(getEvent(eventInfo)))) {
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
preventDefault(getEvent(eventInfo));
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Sets the `action` to `clickonly` for a click event that is not an a11y click
|
|
1914
|
+
* and if there is not already a click action.
|
|
1915
|
+
*/
|
|
1916
|
+
function populateClickOnlyAction(actionElement, eventInfo, actionMap) {
|
|
1917
|
+
if (
|
|
1918
|
+
// If there's already an action, don't attempt to set a CLICKONLY
|
|
1919
|
+
getAction(eventInfo) ||
|
|
1920
|
+
// Only attempt CLICKONLY if the type is CLICK
|
|
1921
|
+
getEventType(eventInfo) !== EventType.CLICK ||
|
|
1922
|
+
// a11y clicks are never CLICKONLY
|
|
1923
|
+
getA11yClickKey(eventInfo) ||
|
|
1924
|
+
actionMap[EventType.CLICKONLY] === undefined) {
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
setEventType(eventInfo, EventType.CLICKONLY);
|
|
1928
|
+
setAction(eventInfo, actionMap[EventType.CLICKONLY], actionElement);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1714
1931
|
/**
|
|
1715
1932
|
* @define Support for accessible click actions. This flag can be overridden in
|
|
1716
1933
|
* a build rule.
|
|
@@ -1819,7 +2036,8 @@ class EventContract {
|
|
|
1819
2036
|
return;
|
|
1820
2037
|
}
|
|
1821
2038
|
if (this.useActionResolver) {
|
|
1822
|
-
this.actionResolver.
|
|
2039
|
+
this.actionResolver.resolveEventType(eventInfo);
|
|
2040
|
+
this.actionResolver.resolveAction(eventInfo);
|
|
1823
2041
|
}
|
|
1824
2042
|
this.dispatcher(eventInfo);
|
|
1825
2043
|
}
|
|
@@ -1840,11 +2058,7 @@ class EventContract {
|
|
|
1840
2058
|
if (eventType in this.eventHandlers || !this.containerManager) {
|
|
1841
2059
|
return;
|
|
1842
2060
|
}
|
|
1843
|
-
if (!EventContract.MOUSE_SPECIAL_SUPPORT &&
|
|
1844
|
-
(eventType === EventType.MOUSEENTER ||
|
|
1845
|
-
eventType === EventType.MOUSELEAVE ||
|
|
1846
|
-
eventType === EventType.POINTERENTER ||
|
|
1847
|
-
eventType === EventType.POINTERLEAVE)) {
|
|
2061
|
+
if (!EventContract.MOUSE_SPECIAL_SUPPORT && NON_BUBBLING_MOUSE_EVENTS.indexOf(eventType) >= 0) {
|
|
1848
2062
|
return;
|
|
1849
2063
|
}
|
|
1850
2064
|
const eventHandler = (eventType, event, container) => {
|
|
@@ -2056,5 +2270,5 @@ function bootstrapEarlyEventContract(field, container, appId, eventTypes, captur
|
|
|
2056
2270
|
eventContract.addEvents(captureEventTypes, true);
|
|
2057
2271
|
}
|
|
2058
2272
|
|
|
2059
|
-
export {
|
|
2273
|
+
export { EventContract, EventContractContainer, EventDispatcher, EventInfoWrapper, EventPhase, bootstrapEarlyEventContract, isCaptureEvent, isSupportedEvent, registerDispatcher };
|
|
2060
2274
|
//# sourceMappingURL=event-dispatch.mjs.map
|