@angular/core 18.0.0-next.4 → 18.0.0-next.6
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/contract_binary.mjs +10 -0
- package/esm2022/primitives/event-dispatch/index.mjs +13 -0
- package/esm2022/primitives/event-dispatch/src/a11y_click.mjs +54 -0
- package/esm2022/primitives/event-dispatch/src/accessibility.mjs +35 -0
- package/esm2022/primitives/event-dispatch/src/attribute.mjs +72 -0
- package/esm2022/primitives/event-dispatch/src/base_dispatcher.mjs +196 -0
- package/esm2022/primitives/event-dispatch/src/cache.mjs +95 -0
- package/esm2022/primitives/event-dispatch/src/char.mjs +35 -0
- package/esm2022/primitives/event-dispatch/src/custom_events.mjs +63 -0
- package/esm2022/primitives/event-dispatch/src/dispatcher.mjs +254 -0
- package/esm2022/primitives/event-dispatch/src/dom.mjs +48 -0
- package/esm2022/primitives/event-dispatch/src/earlyeventcontract.mjs +36 -0
- package/esm2022/primitives/event-dispatch/src/event.mjs +638 -0
- package/esm2022/primitives/event-dispatch/src/event_contract_container.mjs +63 -0
- package/esm2022/primitives/event-dispatch/src/event_contract_defines.mjs +48 -0
- package/esm2022/primitives/event-dispatch/src/event_contract_multi_container.mjs +192 -0
- package/esm2022/primitives/event-dispatch/src/event_handler.mjs +9 -0
- package/esm2022/primitives/event-dispatch/src/event_info.mjs +199 -0
- package/esm2022/primitives/event-dispatch/src/event_type.mjs +244 -0
- package/esm2022/primitives/event-dispatch/src/eventcontract.mjs +675 -0
- package/esm2022/primitives/event-dispatch/src/key_code.mjs +21 -0
- package/esm2022/primitives/event-dispatch/src/legacy_dispatcher.mjs +9 -0
- package/esm2022/primitives/event-dispatch/src/property.mjs +35 -0
- package/esm2022/primitives/event-dispatch/src/register_events.mjs +32 -0
- package/esm2022/primitives/event-dispatch/src/replay.mjs +389 -0
- package/esm2022/primitives/event-dispatch/src/restriction.mjs +15 -0
- package/esm2022/primitives/signals/index.mjs +3 -3
- package/esm2022/primitives/signals/src/computed.mjs +5 -3
- package/esm2022/primitives/signals/src/graph.mjs +14 -9
- package/esm2022/primitives/signals/src/signal.mjs +2 -2
- package/esm2022/primitives/signals/src/watch.mjs +2 -2
- package/esm2022/src/application/application_ref.mjs +4 -2
- package/esm2022/src/change_detection/scheduling/flags.mjs +10 -0
- package/esm2022/src/change_detection/scheduling/ng_zone_scheduling.mjs +18 -33
- package/esm2022/src/change_detection/scheduling/zoneless_scheduling.mjs +1 -1
- package/esm2022/src/change_detection/scheduling/zoneless_scheduling_impl.mjs +111 -9
- package/esm2022/src/core.mjs +3 -1
- package/esm2022/src/core_private_export.mjs +2 -3
- package/esm2022/src/debug/debug_node.mjs +1 -1
- package/esm2022/src/defer/instructions.mjs +31 -8
- package/esm2022/src/di/host_tag_name_token.mjs +65 -0
- package/esm2022/src/di/index.mjs +2 -1
- package/esm2022/src/errors.mjs +1 -1
- package/esm2022/src/hydration/annotate.mjs +15 -1
- package/esm2022/src/hydration/event_replay.mjs +181 -0
- package/esm2022/src/hydration/tokens.mjs +6 -1
- package/esm2022/src/metadata/directives.mjs +1 -62
- package/esm2022/src/pending_tasks.mjs +54 -11
- package/esm2022/src/platform/platform_ref.mjs +3 -3
- package/esm2022/src/render3/component_ref.mjs +1 -1
- package/esm2022/src/render3/context_discovery.mjs +15 -1
- package/esm2022/src/render3/instructions/control_flow.mjs +55 -8
- package/esm2022/src/render3/list_reconciliation.mjs +41 -1
- package/esm2022/src/testability/testability.mjs +2 -30
- package/esm2022/src/util/callback_scheduler.mjs +14 -3
- package/esm2022/src/version.mjs +1 -1
- package/esm2022/src/zone/ng_zone.mjs +22 -6
- package/esm2022/testing/src/async.mjs +2 -10
- package/esm2022/testing/src/component_fixture.mjs +7 -12
- package/esm2022/testing/src/defer.mjs +1 -2
- package/esm2022/testing/src/logger.mjs +3 -3
- package/esm2022/testing/src/test_bed_common.mjs +1 -6
- package/esm2022/testing/src/testing.mjs +1 -2
- package/event-dispatch-contract.min.js +1 -0
- package/fesm2022/core.mjs +608 -174
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +3044 -0
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -0
- package/fesm2022/primitives/signals.mjs +17 -10
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +1 -1
- package/fesm2022/testing.mjs +35 -54
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +199 -115
- package/package.json +10 -1
- package/primitives/event-dispatch/index.d.ts +627 -0
- package/primitives/signals/index.d.ts +1 -1
- package/rxjs-interop/index.d.ts +1 -1
- package/schematics/migrations/{transfer-state → http-providers}/bundle.js +254 -68
- package/schematics/migrations/http-providers/bundle.js.map +7 -0
- package/schematics/migrations/invalid-two-way-bindings/bundle.js +690 -413
- package/schematics/migrations/invalid-two-way-bindings/bundle.js.map +3 -3
- package/schematics/migrations.json +6 -16
- package/schematics/ng-generate/control-flow-migration/bundle.js +693 -417
- package/schematics/ng-generate/control-flow-migration/bundle.js.map +3 -3
- package/schematics/ng-generate/standalone-migration/bundle.js +1072 -735
- package/schematics/ng-generate/standalone-migration/bundle.js.map +3 -3
- package/testing/index.d.ts +2 -15
- package/esm2022/testing/src/private_export.mjs +0 -9
- package/schematics/migrations/block-template-entities/bundle.js +0 -22853
- package/schematics/migrations/block-template-entities/bundle.js.map +0 -7
- package/schematics/migrations/compiler-options/bundle.js +0 -582
- package/schematics/migrations/compiler-options/bundle.js.map +0 -7
- package/schematics/migrations/transfer-state/bundle.js.map +0 -7
|
@@ -0,0 +1,3044 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Angular v18.0.0-next.6
|
|
3
|
+
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Defines special EventInfo and Event properties used when
|
|
9
|
+
* A11Y_SUPPORT_IN_DISPATCHER is enabled.
|
|
10
|
+
*/
|
|
11
|
+
var Attribute$1;
|
|
12
|
+
(function (Attribute) {
|
|
13
|
+
/**
|
|
14
|
+
* An event-type set when the event contract detects a KEYDOWN event but
|
|
15
|
+
* doesn't know if the key press can be treated like a click. The dispatcher
|
|
16
|
+
* will use this event-type to parse the keypress and handle it accordingly.
|
|
17
|
+
*/
|
|
18
|
+
Attribute["MAYBE_CLICK_EVENT_TYPE"] = "maybe_click";
|
|
19
|
+
/**
|
|
20
|
+
* A property added to a dispatched event that had the MAYBE_CLICK_EVENTTYPE
|
|
21
|
+
* event-type but could not be used as a click. The dispatcher sets this
|
|
22
|
+
* property for non-global dispatches before it retriggers the event and it
|
|
23
|
+
* signifies that the event contract should not dispatch this event globally.
|
|
24
|
+
*/
|
|
25
|
+
Attribute["SKIP_GLOBAL_DISPATCH"] = "a11ysgd";
|
|
26
|
+
/**
|
|
27
|
+
* A property added to a dispatched event that had the MAYBE_CLICK_EVENTTYPE
|
|
28
|
+
* event-type but could not be used as a click. The dispatcher sets this
|
|
29
|
+
* property before it retriggers the event and it signifies that the event
|
|
30
|
+
* contract should not look at CLICK actions for KEYDOWN events.
|
|
31
|
+
*/
|
|
32
|
+
Attribute["SKIP_A11Y_CHECK"] = "a11ysc";
|
|
33
|
+
})(Attribute$1 || (Attribute$1 = {}));
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Determines if one node is contained within another. Adapted from
|
|
37
|
+
* {@see goog.dom.contains}.
|
|
38
|
+
* @param node Node that should contain otherNode.
|
|
39
|
+
* @param otherNode Node being contained.
|
|
40
|
+
* @return True if otherNode is contained within node.
|
|
41
|
+
*/
|
|
42
|
+
function contains(node, otherNode) {
|
|
43
|
+
if (otherNode === null) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
// We use browser specific methods for this if available since it is faster
|
|
47
|
+
// that way.
|
|
48
|
+
// IE DOM
|
|
49
|
+
if ('contains' in node && otherNode.nodeType === 1) {
|
|
50
|
+
return node.contains(otherNode);
|
|
51
|
+
}
|
|
52
|
+
// W3C DOM Level 3
|
|
53
|
+
if ('compareDocumentPosition' in node) {
|
|
54
|
+
return node === otherNode || Boolean(node.compareDocumentPosition(otherNode) & 16);
|
|
55
|
+
}
|
|
56
|
+
// W3C DOM Level 1
|
|
57
|
+
while (otherNode && node !== otherNode) {
|
|
58
|
+
otherNode = otherNode.parentNode;
|
|
59
|
+
}
|
|
60
|
+
return otherNode === node;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Helper method for broadcastCustomEvent. Returns true if any member of
|
|
64
|
+
* the set is an ancestor of element.
|
|
65
|
+
*/
|
|
66
|
+
function hasAncestorInNodeList(element, nodeList) {
|
|
67
|
+
for (let idx = 0; idx < nodeList.length; ++idx) {
|
|
68
|
+
const member = nodeList[idx];
|
|
69
|
+
if (member !== element && contains(member, element)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* Names of events that are special to jsaction. These are not all
|
|
78
|
+
* event types that are legal to use in either HTML or the addEvent()
|
|
79
|
+
* API, but these are the ones that are treated specially. All other
|
|
80
|
+
* DOM events can be used in either addEvent() or in the value of the
|
|
81
|
+
* jsaction attribute. Beware of browser specific events or events
|
|
82
|
+
* that don't bubble though: If they are not mentioned here, then
|
|
83
|
+
* event contract doesn't work around their peculiarities.
|
|
84
|
+
*/
|
|
85
|
+
const EventType = {
|
|
86
|
+
/**
|
|
87
|
+
* Mouse middle click, introduced in Chrome 55 and not yet supported on
|
|
88
|
+
* other browsers.
|
|
89
|
+
*/
|
|
90
|
+
AUXCLICK: 'auxclick',
|
|
91
|
+
/**
|
|
92
|
+
* The change event fired by browsers when the `value` attribute of input,
|
|
93
|
+
* select, and textarea elements are changed.
|
|
94
|
+
*/
|
|
95
|
+
CHANGE: 'change',
|
|
96
|
+
/**
|
|
97
|
+
* The click event. In addEvent() refers to all click events, in the
|
|
98
|
+
* jsaction attribute it refers to the unmodified click and Enter/Space
|
|
99
|
+
* keypress events. In the latter case, a jsaction click will be triggered,
|
|
100
|
+
* for accessibility reasons. See clickmod and clickonly, below.
|
|
101
|
+
*/
|
|
102
|
+
CLICK: 'click',
|
|
103
|
+
/**
|
|
104
|
+
* Specifies the jsaction for a modified click event (i.e. a mouse
|
|
105
|
+
* click with the modifier key Cmd/Ctrl pressed). This event isn't
|
|
106
|
+
* separately enabled in addEvent(), because in the DOM, it's just a
|
|
107
|
+
* click event.
|
|
108
|
+
*/
|
|
109
|
+
CLICKMOD: 'clickmod',
|
|
110
|
+
/**
|
|
111
|
+
* Specifies the jsaction for a click-only event. Click-only doesn't take
|
|
112
|
+
* into account the case where an element with focus receives an Enter/Space
|
|
113
|
+
* keypress. This event isn't separately enabled in addEvent().
|
|
114
|
+
*/
|
|
115
|
+
CLICKONLY: 'clickonly',
|
|
116
|
+
/**
|
|
117
|
+
* The dblclick event.
|
|
118
|
+
*/
|
|
119
|
+
DBLCLICK: 'dblclick',
|
|
120
|
+
/**
|
|
121
|
+
* Focus doesn't bubble, but you can use it in addEvent() and
|
|
122
|
+
* jsaction anyway. EventContract does the right thing under the
|
|
123
|
+
* hood.
|
|
124
|
+
*/
|
|
125
|
+
FOCUS: 'focus',
|
|
126
|
+
/**
|
|
127
|
+
* This event only exists in IE. For addEvent() and jsaction, use
|
|
128
|
+
* focus instead; EventContract does the right thing even though
|
|
129
|
+
* focus doesn't bubble.
|
|
130
|
+
*/
|
|
131
|
+
FOCUSIN: 'focusin',
|
|
132
|
+
/**
|
|
133
|
+
* Analog to focus.
|
|
134
|
+
*/
|
|
135
|
+
BLUR: 'blur',
|
|
136
|
+
/**
|
|
137
|
+
* Analog to focusin.
|
|
138
|
+
*/
|
|
139
|
+
FOCUSOUT: 'focusout',
|
|
140
|
+
/**
|
|
141
|
+
* Submit doesn't bubble, so it cannot be used with event
|
|
142
|
+
* contract. However, the browser helpfully fires a click event on
|
|
143
|
+
* the submit button of a form (even if the form is not submitted by
|
|
144
|
+
* a click on the submit button). So you should handle click on the
|
|
145
|
+
* submit button instead.
|
|
146
|
+
*/
|
|
147
|
+
SUBMIT: 'submit',
|
|
148
|
+
/**
|
|
149
|
+
* The keydown event. In addEvent() and non-click jsaction it represents the
|
|
150
|
+
* regular DOM keydown event. It represents click actions in non-Gecko
|
|
151
|
+
* browsers.
|
|
152
|
+
*/
|
|
153
|
+
KEYDOWN: 'keydown',
|
|
154
|
+
/**
|
|
155
|
+
* The keypress event. In addEvent() and non-click jsaction it represents the
|
|
156
|
+
* regular DOM keypress event. It represents click actions in Gecko browsers.
|
|
157
|
+
*/
|
|
158
|
+
KEYPRESS: 'keypress',
|
|
159
|
+
/**
|
|
160
|
+
* The keyup event. In addEvent() and non-click jsaction it represents the
|
|
161
|
+
* regular DOM keyup event. It represents click actions in non-Gecko
|
|
162
|
+
* browsers.
|
|
163
|
+
*/
|
|
164
|
+
KEYUP: 'keyup',
|
|
165
|
+
/**
|
|
166
|
+
* The mouseup event. Can either be used directly or used implicitly to
|
|
167
|
+
* capture mouseup events. In addEvent(), it represents a regular DOM
|
|
168
|
+
* mouseup event.
|
|
169
|
+
*/
|
|
170
|
+
MOUSEUP: 'mouseup',
|
|
171
|
+
/**
|
|
172
|
+
* The mousedown event. Can either be used directly or used implicitly to
|
|
173
|
+
* capture mouseenter events. In addEvent(), it represents a regular DOM
|
|
174
|
+
* mouseover event.
|
|
175
|
+
*/
|
|
176
|
+
MOUSEDOWN: 'mousedown',
|
|
177
|
+
/**
|
|
178
|
+
* The mouseover event. Can either be used directly or used implicitly to
|
|
179
|
+
* capture mouseenter events. In addEvent(), it represents a regular DOM
|
|
180
|
+
* mouseover event.
|
|
181
|
+
*/
|
|
182
|
+
MOUSEOVER: 'mouseover',
|
|
183
|
+
/**
|
|
184
|
+
* The mouseout event. Can either be used directly or used implicitly to
|
|
185
|
+
* capture mouseover events. In addEvent(), it represents a regular DOM
|
|
186
|
+
* mouseout event.
|
|
187
|
+
*/
|
|
188
|
+
MOUSEOUT: 'mouseout',
|
|
189
|
+
/**
|
|
190
|
+
* The mouseenter event. Does not bubble and fires individually on each
|
|
191
|
+
* element being entered within a DOM tree.
|
|
192
|
+
*/
|
|
193
|
+
MOUSEENTER: 'mouseenter',
|
|
194
|
+
/**
|
|
195
|
+
* The mouseleave event. Does not bubble and fires individually on each
|
|
196
|
+
* element being entered within a DOM tree.
|
|
197
|
+
*/
|
|
198
|
+
MOUSELEAVE: 'mouseleave',
|
|
199
|
+
/**
|
|
200
|
+
* The mousemove event.
|
|
201
|
+
*/
|
|
202
|
+
MOUSEMOVE: 'mousemove',
|
|
203
|
+
/**
|
|
204
|
+
* The pointerup event. Can either be used directly or used implicitly to
|
|
205
|
+
* capture pointerup events. In addEvent(), it represents a regular DOM
|
|
206
|
+
* pointerup event.
|
|
207
|
+
*/
|
|
208
|
+
POINTERUP: 'pointerup',
|
|
209
|
+
/**
|
|
210
|
+
* The pointerdown event. Can either be used directly or used implicitly to
|
|
211
|
+
* capture pointerenter events. In addEvent(), it represents a regular DOM
|
|
212
|
+
* mouseover event.
|
|
213
|
+
*/
|
|
214
|
+
POINTERDOWN: 'pointerdown',
|
|
215
|
+
/**
|
|
216
|
+
* The pointerover event. Can either be used directly or used implicitly to
|
|
217
|
+
* capture pointerenter events. In addEvent(), it represents a regular DOM
|
|
218
|
+
* pointerover event.
|
|
219
|
+
*/
|
|
220
|
+
POINTEROVER: 'pointerover',
|
|
221
|
+
/**
|
|
222
|
+
* The pointerout event. Can either be used directly or used implicitly to
|
|
223
|
+
* capture pointerover events. In addEvent(), it represents a regular DOM
|
|
224
|
+
* pointerout event.
|
|
225
|
+
*/
|
|
226
|
+
POINTEROUT: 'pointerout',
|
|
227
|
+
/**
|
|
228
|
+
* The pointerenter event. Does not bubble and fires individually on each
|
|
229
|
+
* element being entered within a DOM tree.
|
|
230
|
+
*/
|
|
231
|
+
POINTERENTER: 'pointerenter',
|
|
232
|
+
/**
|
|
233
|
+
* The pointerleave event. Does not bubble and fires individually on each
|
|
234
|
+
* element being entered within a DOM tree.
|
|
235
|
+
*/
|
|
236
|
+
POINTERLEAVE: 'pointerleave',
|
|
237
|
+
/**
|
|
238
|
+
* The pointermove event.
|
|
239
|
+
*/
|
|
240
|
+
POINTERMOVE: 'pointermove',
|
|
241
|
+
/**
|
|
242
|
+
* The pointercancel event.
|
|
243
|
+
*/
|
|
244
|
+
POINTERCANCEL: 'pointercancel',
|
|
245
|
+
/**
|
|
246
|
+
* The gotpointercapture event is fired when
|
|
247
|
+
* Element.setPointerCapture(pointerId) is called on a mouse input, or
|
|
248
|
+
* implicitly when a touch input begins.
|
|
249
|
+
*/
|
|
250
|
+
GOTPOINTERCAPTURE: 'gotpointercapture',
|
|
251
|
+
/**
|
|
252
|
+
* The lostpointercapture event is fired when
|
|
253
|
+
* Element.releasePointerCapture(pointerId) is called, or implicitly after a
|
|
254
|
+
* touch input ends.
|
|
255
|
+
*/
|
|
256
|
+
LOSTPOINTERCAPTURE: 'lostpointercapture',
|
|
257
|
+
/**
|
|
258
|
+
* The error event. The error event doesn't bubble, but you can use it in
|
|
259
|
+
* addEvent() and jsaction anyway. EventContract does the right thing under
|
|
260
|
+
* the hood (except in IE8 which does not use error events).
|
|
261
|
+
*/
|
|
262
|
+
ERROR: 'error',
|
|
263
|
+
/**
|
|
264
|
+
* The load event. The load event doesn't bubble, but you can use it in
|
|
265
|
+
* addEvent() and jsaction anyway. EventContract does the right thing
|
|
266
|
+
* under the hood.
|
|
267
|
+
*/
|
|
268
|
+
LOAD: 'load',
|
|
269
|
+
/**
|
|
270
|
+
* The unload event.
|
|
271
|
+
*/
|
|
272
|
+
UNLOAD: 'unload',
|
|
273
|
+
/**
|
|
274
|
+
* The touchstart event. Bubbles, will only ever fire in browsers with
|
|
275
|
+
* touch support.
|
|
276
|
+
*/
|
|
277
|
+
TOUCHSTART: 'touchstart',
|
|
278
|
+
/**
|
|
279
|
+
* The touchend event. Bubbles, will only ever fire in browsers with
|
|
280
|
+
* touch support.
|
|
281
|
+
*/
|
|
282
|
+
TOUCHEND: 'touchend',
|
|
283
|
+
/**
|
|
284
|
+
* The touchmove event. Bubbles, will only ever fire in browsers with
|
|
285
|
+
* touch support.
|
|
286
|
+
*/
|
|
287
|
+
TOUCHMOVE: 'touchmove',
|
|
288
|
+
/**
|
|
289
|
+
* The input event.
|
|
290
|
+
*/
|
|
291
|
+
INPUT: 'input',
|
|
292
|
+
/**
|
|
293
|
+
* The scroll event.
|
|
294
|
+
*/
|
|
295
|
+
SCROLL: 'scroll',
|
|
296
|
+
/**
|
|
297
|
+
* The toggle event. The toggle event doesn't bubble, but you can use it in
|
|
298
|
+
* addEvent() and jsaction anyway. EventContract does the right thing
|
|
299
|
+
* under the hood.
|
|
300
|
+
*/
|
|
301
|
+
TOGGLE: 'toggle',
|
|
302
|
+
/**
|
|
303
|
+
* A custom event. The actual custom event type is declared as the 'type'
|
|
304
|
+
* field in the event details. Supported in Firefox 6+, IE 9+, and all Chrome
|
|
305
|
+
* versions.
|
|
306
|
+
*
|
|
307
|
+
* This is an internal name. Users should use jsaction's fireCustomEvent to
|
|
308
|
+
* fire custom events instead of relying on this type to create them.
|
|
309
|
+
*/
|
|
310
|
+
CUSTOM: '_custom',
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/** Special keycodes used by jsaction for the generic click action. */
|
|
314
|
+
var KeyCode;
|
|
315
|
+
(function (KeyCode) {
|
|
316
|
+
/**
|
|
317
|
+
* If on a Macintosh with an extended keyboard, the Enter key located in the
|
|
318
|
+
* numeric pad has a different ASCII code.
|
|
319
|
+
*/
|
|
320
|
+
KeyCode[KeyCode["MAC_ENTER"] = 3] = "MAC_ENTER";
|
|
321
|
+
/** The Enter key. */
|
|
322
|
+
KeyCode[KeyCode["ENTER"] = 13] = "ENTER";
|
|
323
|
+
/** The Space key. */
|
|
324
|
+
KeyCode[KeyCode["SPACE"] = 32] = "SPACE";
|
|
325
|
+
})(KeyCode || (KeyCode = {}));
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Gets a browser event type, if it would differ from the JSAction event type.
|
|
329
|
+
*/
|
|
330
|
+
function getBrowserEventType(eventType) {
|
|
331
|
+
// Mouseenter and mouseleave events are not handled directly because they
|
|
332
|
+
// are not available everywhere. In browsers where they are available, they
|
|
333
|
+
// don't bubble and aren't visible at the container boundary. Instead, we
|
|
334
|
+
// synthesize the mouseenter and mouseleave events from mouseover and
|
|
335
|
+
// mouseout events, respectively. Cf. eventcontract.js.
|
|
336
|
+
if (eventType === EventType.MOUSEENTER) {
|
|
337
|
+
return EventType.MOUSEOVER;
|
|
338
|
+
}
|
|
339
|
+
else if (eventType === EventType.MOUSELEAVE) {
|
|
340
|
+
return EventType.MOUSEOUT;
|
|
341
|
+
}
|
|
342
|
+
else if (eventType === EventType.POINTERENTER) {
|
|
343
|
+
return EventType.POINTEROVER;
|
|
344
|
+
}
|
|
345
|
+
else if (eventType === EventType.POINTERLEAVE) {
|
|
346
|
+
return EventType.POINTEROUT;
|
|
347
|
+
}
|
|
348
|
+
return eventType;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Registers the event handler function with the given DOM element for
|
|
352
|
+
* the given event type.
|
|
353
|
+
*
|
|
354
|
+
* @param element The element.
|
|
355
|
+
* @param eventType The event type.
|
|
356
|
+
* @param handler The handler function to install.
|
|
357
|
+
* @return Information needed to uninstall the event handler eventually.
|
|
358
|
+
*/
|
|
359
|
+
function addEventListener(element, eventType, handler) {
|
|
360
|
+
// All event handlers are registered in the bubbling
|
|
361
|
+
// phase.
|
|
362
|
+
//
|
|
363
|
+
// All browsers support focus and blur, but these events only are propagated
|
|
364
|
+
// in the capture phase. Very legacy browsers do not support focusin or
|
|
365
|
+
// focusout.
|
|
366
|
+
//
|
|
367
|
+
// It would be a bad idea to register all event handlers in the
|
|
368
|
+
// capture phase because then regular onclick handlers would not be
|
|
369
|
+
// executed at all on events that trigger a jsaction. That's not
|
|
370
|
+
// entirely what we want, at least for now.
|
|
371
|
+
//
|
|
372
|
+
// Error and load events (i.e. on images) do not bubble so they are also
|
|
373
|
+
// handled in the capture phase.
|
|
374
|
+
let capture = false;
|
|
375
|
+
if (eventType === EventType.FOCUS ||
|
|
376
|
+
eventType === EventType.BLUR ||
|
|
377
|
+
eventType === EventType.ERROR ||
|
|
378
|
+
eventType === EventType.LOAD ||
|
|
379
|
+
eventType === EventType.TOGGLE) {
|
|
380
|
+
capture = true;
|
|
381
|
+
}
|
|
382
|
+
element.addEventListener(eventType, handler, capture);
|
|
383
|
+
return { eventType, handler, capture };
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Removes the event handler for the given event from the element.
|
|
387
|
+
* the given event type.
|
|
388
|
+
*
|
|
389
|
+
* @param element The element.
|
|
390
|
+
* @param info The information needed to deregister the handler, as returned by
|
|
391
|
+
* addEventListener(), above.
|
|
392
|
+
*/
|
|
393
|
+
function removeEventListener(element, info) {
|
|
394
|
+
if (element.removeEventListener) {
|
|
395
|
+
element.removeEventListener(info.eventType, info.handler, info.capture);
|
|
396
|
+
// `detachEvent` is an old DOM API.
|
|
397
|
+
// tslint:disable-next-line:no-any
|
|
398
|
+
}
|
|
399
|
+
else if (element.detachEvent) {
|
|
400
|
+
// `detachEvent` is an old DOM API.
|
|
401
|
+
// tslint:disable-next-line:no-any
|
|
402
|
+
element.detachEvent(`on${info.eventType}`, info.handler);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Cancels propagation of an event.
|
|
407
|
+
* @param e The event to cancel propagation for.
|
|
408
|
+
*/
|
|
409
|
+
function stopPropagation$1(e) {
|
|
410
|
+
e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Prevents the default action of an event.
|
|
414
|
+
* @param e The event to prevent the default action for.
|
|
415
|
+
*/
|
|
416
|
+
function preventDefault(e) {
|
|
417
|
+
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Gets the target Element of the event. In Firefox, a text node may appear as
|
|
421
|
+
* the target of the event, in which case we return the parent element of the
|
|
422
|
+
* text node.
|
|
423
|
+
* @param e The event to get the target of.
|
|
424
|
+
* @return The target element.
|
|
425
|
+
*/
|
|
426
|
+
function getTarget(e) {
|
|
427
|
+
let el = e.target;
|
|
428
|
+
// In Firefox, the event may have a text node as its target. We always
|
|
429
|
+
// want the parent Element the text node belongs to, however.
|
|
430
|
+
if (!el.getAttribute && el.parentNode) {
|
|
431
|
+
el = el.parentNode;
|
|
432
|
+
}
|
|
433
|
+
return el;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Whether we are on a Mac. Not pulling in useragent just for this.
|
|
437
|
+
*/
|
|
438
|
+
let isMac = typeof navigator !== 'undefined' && /Macintosh/.test(navigator.userAgent);
|
|
439
|
+
/**
|
|
440
|
+
* Determines and returns whether the given event (which is assumed to be a
|
|
441
|
+
* click event) is a middle click.
|
|
442
|
+
* NOTE: There is not a consistent way to identify middle click
|
|
443
|
+
* http://www.unixpapa.com/js/mouse.html
|
|
444
|
+
*/
|
|
445
|
+
function isMiddleClick(e) {
|
|
446
|
+
return (
|
|
447
|
+
// `which` is an old DOM API.
|
|
448
|
+
// tslint:disable-next-line:no-any
|
|
449
|
+
e.which === 2 ||
|
|
450
|
+
// `which` is an old DOM API.
|
|
451
|
+
// tslint:disable-next-line:no-any
|
|
452
|
+
(e.which == null &&
|
|
453
|
+
// `button` is an old DOM API.
|
|
454
|
+
// tslint:disable-next-line:no-any
|
|
455
|
+
e.button === 4) // middle click for IE
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Determines and returns whether the given event (which is assumed
|
|
460
|
+
* to be a click event) is modified. A middle click is considered a modified
|
|
461
|
+
* click to retain the default browser action, which opens a link in a new tab.
|
|
462
|
+
* @param e The event.
|
|
463
|
+
* @return Whether the given event is modified.
|
|
464
|
+
*/
|
|
465
|
+
function isModifiedClickEvent(e) {
|
|
466
|
+
return (
|
|
467
|
+
// `metaKey` is an old DOM API.
|
|
468
|
+
// tslint:disable-next-line:no-any
|
|
469
|
+
(isMac && e.metaKey) ||
|
|
470
|
+
// `ctrlKey` is an old DOM API.
|
|
471
|
+
// tslint:disable-next-line:no-any
|
|
472
|
+
(!isMac && e.ctrlKey) ||
|
|
473
|
+
isMiddleClick(e) ||
|
|
474
|
+
// `shiftKey` is an old DOM API.
|
|
475
|
+
// tslint:disable-next-line:no-any
|
|
476
|
+
e.shiftKey);
|
|
477
|
+
}
|
|
478
|
+
/** Whether we are on WebKit (e.g., Chrome). */
|
|
479
|
+
const isWebKit = typeof navigator !== 'undefined' &&
|
|
480
|
+
!/Opera/.test(navigator.userAgent) &&
|
|
481
|
+
/WebKit/.test(navigator.userAgent);
|
|
482
|
+
/** Whether we are on IE. */
|
|
483
|
+
const isIe = typeof navigator !== 'undefined' &&
|
|
484
|
+
(/MSIE/.test(navigator.userAgent) || /Trident/.test(navigator.userAgent));
|
|
485
|
+
/** Whether we are on Gecko (e.g., Firefox). */
|
|
486
|
+
const isGecko = typeof navigator !== 'undefined' &&
|
|
487
|
+
!/Opera|WebKit/.test(navigator.userAgent) &&
|
|
488
|
+
/Gecko/.test(navigator.product);
|
|
489
|
+
/**
|
|
490
|
+
* Determines and returns whether the given element is a valid target for
|
|
491
|
+
* keypress/keydown DOM events that act like regular DOM clicks.
|
|
492
|
+
* @param el The element.
|
|
493
|
+
* @return Whether the given element is a valid action key target.
|
|
494
|
+
*/
|
|
495
|
+
function isValidActionKeyTarget(el) {
|
|
496
|
+
if (!('getAttribute' in el)) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
if (isTextControl(el)) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
if (isNativelyActivatable(el)) {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
// `isContentEditable` is an old DOM API.
|
|
506
|
+
// tslint:disable-next-line:no-any
|
|
507
|
+
if (el.isContentEditable) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Whether an event has a modifier key activated.
|
|
514
|
+
* @param e The event.
|
|
515
|
+
* @return True, if a modifier key is activated.
|
|
516
|
+
*/
|
|
517
|
+
function hasModifierKey(e) {
|
|
518
|
+
return (
|
|
519
|
+
// `ctrlKey` is an old DOM API.
|
|
520
|
+
// tslint:disable-next-line:no-any
|
|
521
|
+
e.ctrlKey ||
|
|
522
|
+
// `shiftKey` is an old DOM API.
|
|
523
|
+
// tslint:disable-next-line:no-any
|
|
524
|
+
e.shiftKey ||
|
|
525
|
+
// `altKey` is an old DOM API.
|
|
526
|
+
// tslint:disable-next-line:no-any
|
|
527
|
+
e.altKey ||
|
|
528
|
+
// `metaKey` is an old DOM API.
|
|
529
|
+
// tslint:disable-next-line:no-any
|
|
530
|
+
e.metaKey);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Determines and returns whether the given event has a target that already
|
|
534
|
+
* has event handlers attached because it is a native HTML control. Used to
|
|
535
|
+
* determine if preventDefault should be called when isActionKeyEvent is true.
|
|
536
|
+
* @param e The event.
|
|
537
|
+
* @return If preventDefault should be called.
|
|
538
|
+
*/
|
|
539
|
+
function shouldCallPreventDefaultOnNativeHtmlControl(e) {
|
|
540
|
+
const el = getTarget(e);
|
|
541
|
+
const tagName = el.tagName.toUpperCase();
|
|
542
|
+
const role = (el.getAttribute('role') || '').toUpperCase();
|
|
543
|
+
if (tagName === 'BUTTON' || role === 'BUTTON') {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
if (!isNativeHTMLControl(el)) {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
if (tagName === 'A') {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Fix for physical d-pads on feature phone platforms; the native event
|
|
554
|
+
* (ie. isTrusted: true) needs to fire to show the OPTION list. See
|
|
555
|
+
* b/135288469 for more info.
|
|
556
|
+
*/
|
|
557
|
+
if (tagName === 'SELECT') {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
if (processSpace(el)) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
if (isTextControl(el)) {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Determines and returns whether the given event acts like a regular DOM click,
|
|
570
|
+
* and should be handled instead of the click. If this returns true, the caller
|
|
571
|
+
* will call preventDefault() to prevent a possible duplicate event.
|
|
572
|
+
* This is represented by a keypress (keydown on Gecko browsers) on Enter or
|
|
573
|
+
* Space key.
|
|
574
|
+
* @param e The event.
|
|
575
|
+
* @return True, if the event emulates a DOM click.
|
|
576
|
+
*/
|
|
577
|
+
function isActionKeyEvent(e) {
|
|
578
|
+
let key =
|
|
579
|
+
// `which` is an old DOM API.
|
|
580
|
+
// tslint:disable-next-line:no-any
|
|
581
|
+
e.which ||
|
|
582
|
+
// `keyCode` is an old DOM API.
|
|
583
|
+
// tslint:disable-next-line:no-any
|
|
584
|
+
e.keyCode;
|
|
585
|
+
if (!key && e.key) {
|
|
586
|
+
key = ACTION_KEY_TO_KEYCODE[e.key];
|
|
587
|
+
}
|
|
588
|
+
if (isWebKit && key === KeyCode.MAC_ENTER) {
|
|
589
|
+
key = KeyCode.ENTER;
|
|
590
|
+
}
|
|
591
|
+
if (key !== KeyCode.ENTER && key !== KeyCode.SPACE) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
const el = getTarget(e);
|
|
595
|
+
if (e.type !== EventType.KEYDOWN || !isValidActionKeyTarget(el) || hasModifierKey(e)) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
// For <input type="checkbox">, we must only handle the browser's native click
|
|
599
|
+
// event, so that the browser can toggle the checkbox.
|
|
600
|
+
if (processSpace(el) && key === KeyCode.SPACE) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
// If this element is non-focusable, ignore stray keystrokes (b/18337209)
|
|
604
|
+
// Sscreen readers can move without tab focus, so any tabIndex is focusable.
|
|
605
|
+
// See B/21809604
|
|
606
|
+
if (!isFocusable(el)) {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
const type = (el.getAttribute('role') ||
|
|
610
|
+
el.type ||
|
|
611
|
+
el.tagName).toUpperCase();
|
|
612
|
+
const isSpecificTriggerKey = IDENTIFIER_TO_KEY_TRIGGER_MAPPING[type] % key === 0;
|
|
613
|
+
const isDefaultTriggerKey = !(type in IDENTIFIER_TO_KEY_TRIGGER_MAPPING) && key === KeyCode.ENTER;
|
|
614
|
+
const hasType = el.tagName.toUpperCase() !== 'INPUT' || !!el.type;
|
|
615
|
+
return (isSpecificTriggerKey || isDefaultTriggerKey) && hasType;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Checks whether a DOM element can receive keyboard focus.
|
|
619
|
+
* This code is based on goog.dom.isFocusable, but simplified since we shouldn't
|
|
620
|
+
* care about visibility if we're already handling a keyboard event.
|
|
621
|
+
*/
|
|
622
|
+
function isFocusable(el) {
|
|
623
|
+
return ((el.tagName in NATIVELY_FOCUSABLE_ELEMENTS || hasSpecifiedTabIndex(el)) &&
|
|
624
|
+
!el.disabled);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* @param element Element to check.
|
|
628
|
+
* @return Whether the element has a specified tab index.
|
|
629
|
+
*/
|
|
630
|
+
function hasSpecifiedTabIndex(element) {
|
|
631
|
+
// IE returns 0 for an unset tabIndex, so we must use getAttributeNode(),
|
|
632
|
+
// which returns an object with a 'specified' property if tabIndex is
|
|
633
|
+
// specified. This works on other browsers, too.
|
|
634
|
+
const attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
|
|
635
|
+
return attrNode != null && attrNode.specified;
|
|
636
|
+
}
|
|
637
|
+
/** Element tagnames that are focusable by default. */
|
|
638
|
+
const NATIVELY_FOCUSABLE_ELEMENTS = {
|
|
639
|
+
'A': 1,
|
|
640
|
+
'INPUT': 1,
|
|
641
|
+
'TEXTAREA': 1,
|
|
642
|
+
'SELECT': 1,
|
|
643
|
+
'BUTTON': 1,
|
|
644
|
+
};
|
|
645
|
+
/** @return True, if the Space key was pressed. */
|
|
646
|
+
function isSpaceKeyEvent(e) {
|
|
647
|
+
const key =
|
|
648
|
+
// `which` is an old DOM API.
|
|
649
|
+
// tslint:disable-next-line:no-any
|
|
650
|
+
e.which ||
|
|
651
|
+
// `keyCode` is an old DOM API.
|
|
652
|
+
// tslint:disable-next-line:no-any
|
|
653
|
+
e.keyCode;
|
|
654
|
+
const el = getTarget(e);
|
|
655
|
+
const elementName = (el.type || el.tagName).toUpperCase();
|
|
656
|
+
return key === KeyCode.SPACE && elementName !== 'CHECKBOX';
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Determines whether the event corresponds to a non-bubbling mouse
|
|
660
|
+
* event type (mouseenter, mouseleave, pointerenter, and pointerleave).
|
|
661
|
+
*
|
|
662
|
+
* During mouseover (mouseenter) and pointerover (pointerenter), the
|
|
663
|
+
* relatedTarget is the element being entered from. During mouseout (mouseleave)
|
|
664
|
+
* and pointerout (pointerleave), the relatedTarget is the element being exited
|
|
665
|
+
* to.
|
|
666
|
+
*
|
|
667
|
+
* In both cases, if relatedTarget is outside target, then the corresponding
|
|
668
|
+
* special event has occurred, otherwise it hasn't.
|
|
669
|
+
*
|
|
670
|
+
* @param e The mouseover/mouseout event.
|
|
671
|
+
* @param type The type of the mouse special event.
|
|
672
|
+
* @param element The element on which the jsaction for the
|
|
673
|
+
* mouseenter/mouseleave event is defined.
|
|
674
|
+
* @return True if the event is a mouseenter/mouseleave event.
|
|
675
|
+
*/
|
|
676
|
+
function isMouseSpecialEvent(e, type, element) {
|
|
677
|
+
// `relatedTarget` is an old DOM API.
|
|
678
|
+
// tslint:disable-next-line:no-any
|
|
679
|
+
const related = e.relatedTarget;
|
|
680
|
+
return (((e.type === EventType.MOUSEOVER && type === EventType.MOUSEENTER) ||
|
|
681
|
+
(e.type === EventType.MOUSEOUT && type === EventType.MOUSELEAVE) ||
|
|
682
|
+
(e.type === EventType.POINTEROVER && type === EventType.POINTERENTER) ||
|
|
683
|
+
(e.type === EventType.POINTEROUT && type === EventType.POINTERLEAVE)) &&
|
|
684
|
+
(!related || (related !== element && !contains(element, related))));
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Creates a new EventLike object for a mouseenter/mouseleave event that's
|
|
688
|
+
* derived from the original corresponding mouseover/mouseout event.
|
|
689
|
+
* @param e The event.
|
|
690
|
+
* @param target The element on which the jsaction for the mouseenter/mouseleave
|
|
691
|
+
* event is defined.
|
|
692
|
+
* @return A modified event-like object copied from the event object passed into
|
|
693
|
+
* this function.
|
|
694
|
+
*/
|
|
695
|
+
function createMouseSpecialEvent(e, target) {
|
|
696
|
+
// We have to create a copy of the event object because we need to mutate
|
|
697
|
+
// its fields. We do this for the special mouse events because the event
|
|
698
|
+
// target needs to be retargeted to the action element rather than the real
|
|
699
|
+
// element (since we are simulating the special mouse events with mouseover/
|
|
700
|
+
// mouseout).
|
|
701
|
+
//
|
|
702
|
+
// Since we're making a copy anyways, we might as well attempt to convert
|
|
703
|
+
// this event into a pseudo-real mouseenter/mouseleave event by adjusting
|
|
704
|
+
// its type.
|
|
705
|
+
//
|
|
706
|
+
// tslint:disable-next-line:no-any
|
|
707
|
+
const copy = {};
|
|
708
|
+
for (const property in e) {
|
|
709
|
+
if (property === 'srcElement' || property === 'target') {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const key = property;
|
|
713
|
+
// Making a copy requires iterating through all properties of `Event`.
|
|
714
|
+
// tslint:disable-next-line:no-dict-access-on-struct-type
|
|
715
|
+
const value = e[key];
|
|
716
|
+
if (typeof value === 'function') {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
// Value should be the expected type, but the value of `key` is not known
|
|
720
|
+
// statically.
|
|
721
|
+
// tslint:disable-next-line:no-any
|
|
722
|
+
copy[key] = value;
|
|
723
|
+
}
|
|
724
|
+
if (e.type === EventType.MOUSEOVER) {
|
|
725
|
+
copy['type'] = EventType.MOUSEENTER;
|
|
726
|
+
}
|
|
727
|
+
else if (e.type === EventType.MOUSEOUT) {
|
|
728
|
+
copy['type'] = EventType.MOUSELEAVE;
|
|
729
|
+
}
|
|
730
|
+
else if (e.type === EventType.POINTEROVER) {
|
|
731
|
+
copy['type'] = EventType.POINTERENTER;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
copy['type'] = EventType.POINTERLEAVE;
|
|
735
|
+
}
|
|
736
|
+
copy['target'] = copy['srcElement'] = target;
|
|
737
|
+
copy['bubbles'] = false;
|
|
738
|
+
return copy;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Returns touch data extracted from the touch event: clientX, clientY, screenX
|
|
742
|
+
* and screenY. If the event has no touch information at all, the returned
|
|
743
|
+
* value is null.
|
|
744
|
+
*
|
|
745
|
+
* The fields of this Object are unquoted.
|
|
746
|
+
*
|
|
747
|
+
* @param event A touch event.
|
|
748
|
+
*/
|
|
749
|
+
function getTouchData(event) {
|
|
750
|
+
const touch = (event.changedTouches && event.changedTouches[0]) || (event.touches && event.touches[0]);
|
|
751
|
+
if (!touch) {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
clientX: touch.clientX,
|
|
756
|
+
clientY: touch.clientY,
|
|
757
|
+
screenX: touch.screenX,
|
|
758
|
+
screenY: touch.screenY,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Creates a new EventLike object for a "click" event that's derived from the
|
|
763
|
+
* original corresponding "touchend" event for a fast-click implementation.
|
|
764
|
+
*
|
|
765
|
+
* It takes a touch event, adds common fields found in a click event and
|
|
766
|
+
* changes the type to 'click', so that the resulting event looks more like
|
|
767
|
+
* a real click event.
|
|
768
|
+
*
|
|
769
|
+
* @param event A touch event.
|
|
770
|
+
* @return A modified event-like object copied from the event object passed into
|
|
771
|
+
* this function.
|
|
772
|
+
*/
|
|
773
|
+
function recreateTouchEventAsClick(event) {
|
|
774
|
+
const click = {};
|
|
775
|
+
click['originalEventType'] = event.type;
|
|
776
|
+
click['type'] = EventType.CLICK;
|
|
777
|
+
for (const property in event) {
|
|
778
|
+
if (property === 'type' || property === 'srcElement') {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
const key = property;
|
|
782
|
+
// Making a copy requires iterating through all properties of `TouchEvent`.
|
|
783
|
+
// tslint:disable-next-line:no-dict-access-on-struct-type
|
|
784
|
+
const value = event[key];
|
|
785
|
+
if (typeof value === 'function') {
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
// Value should be the expected type, but the value of `key` is not known
|
|
789
|
+
// statically.
|
|
790
|
+
// tslint:disable-next-line:no-any
|
|
791
|
+
click[key] = value;
|
|
792
|
+
}
|
|
793
|
+
// Ensure that the event has the most recent timestamp. This timestamp
|
|
794
|
+
// may be used in the future to validate or cancel subsequent click events.
|
|
795
|
+
click['timeStamp'] = Date.now();
|
|
796
|
+
// Emulate preventDefault and stopPropagation behavior
|
|
797
|
+
click['defaultPrevented'] = false;
|
|
798
|
+
click['preventDefault'] = syntheticPreventDefault;
|
|
799
|
+
click['_propagationStopped'] = false;
|
|
800
|
+
click['stopPropagation'] = syntheticStopPropagation;
|
|
801
|
+
// Emulate click coordinates using touch info
|
|
802
|
+
const touch = getTouchData(event);
|
|
803
|
+
if (touch) {
|
|
804
|
+
click['clientX'] = touch.clientX;
|
|
805
|
+
click['clientY'] = touch.clientY;
|
|
806
|
+
click['screenX'] = touch.screenX;
|
|
807
|
+
click['screenY'] = touch.screenY;
|
|
808
|
+
}
|
|
809
|
+
return click;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* An implementation of "preventDefault" for a synthesized event. Simply
|
|
813
|
+
* sets "defaultPrevented" property to true.
|
|
814
|
+
*/
|
|
815
|
+
function syntheticPreventDefault() {
|
|
816
|
+
this.defaultPrevented = true;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* An implementation of "stopPropagation" for a synthesized event. It simply
|
|
820
|
+
* sets a synthetic non-standard "_propagationStopped" property to true.
|
|
821
|
+
*/
|
|
822
|
+
function syntheticStopPropagation() {
|
|
823
|
+
this._propagationStopped = true;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Mapping of KeyboardEvent.key values to
|
|
827
|
+
* KeyCode values.
|
|
828
|
+
*/
|
|
829
|
+
const ACTION_KEY_TO_KEYCODE = {
|
|
830
|
+
'Enter': KeyCode.ENTER,
|
|
831
|
+
' ': KeyCode.SPACE,
|
|
832
|
+
};
|
|
833
|
+
/**
|
|
834
|
+
* Mapping of HTML element identifiers (ARIA role, type, or tagName) to the
|
|
835
|
+
* keys (enter and/or space) that should activate them. A value of zero means
|
|
836
|
+
* that both should activate them.
|
|
837
|
+
*/
|
|
838
|
+
const IDENTIFIER_TO_KEY_TRIGGER_MAPPING = {
|
|
839
|
+
'A': KeyCode.ENTER,
|
|
840
|
+
'BUTTON': 0,
|
|
841
|
+
'CHECKBOX': KeyCode.SPACE,
|
|
842
|
+
'COMBOBOX': KeyCode.ENTER,
|
|
843
|
+
'FILE': 0,
|
|
844
|
+
'GRIDCELL': KeyCode.ENTER,
|
|
845
|
+
'LINK': KeyCode.ENTER,
|
|
846
|
+
'LISTBOX': KeyCode.ENTER,
|
|
847
|
+
'MENU': 0,
|
|
848
|
+
'MENUBAR': 0,
|
|
849
|
+
'MENUITEM': 0,
|
|
850
|
+
'MENUITEMCHECKBOX': 0,
|
|
851
|
+
'MENUITEMRADIO': 0,
|
|
852
|
+
'OPTION': 0,
|
|
853
|
+
'RADIO': KeyCode.SPACE,
|
|
854
|
+
'RADIOGROUP': KeyCode.SPACE,
|
|
855
|
+
'RESET': 0,
|
|
856
|
+
'SUBMIT': 0,
|
|
857
|
+
'SWITCH': KeyCode.SPACE,
|
|
858
|
+
'TAB': 0,
|
|
859
|
+
'TREE': KeyCode.ENTER,
|
|
860
|
+
'TREEITEM': KeyCode.ENTER,
|
|
861
|
+
};
|
|
862
|
+
/**
|
|
863
|
+
* Returns whether or not to process space based on the type of the element;
|
|
864
|
+
* checks to make sure that type is not null.
|
|
865
|
+
* @param element The element.
|
|
866
|
+
* @return Whether or not to process space based on type.
|
|
867
|
+
*/
|
|
868
|
+
function processSpace(element) {
|
|
869
|
+
const type = (element.getAttribute('type') || element.tagName).toUpperCase();
|
|
870
|
+
return type in PROCESS_SPACE;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Returns whether or not the given element is a text control.
|
|
874
|
+
* @param el The element.
|
|
875
|
+
* @return Whether or not the given element is a text control.
|
|
876
|
+
*/
|
|
877
|
+
function isTextControl(el) {
|
|
878
|
+
const type = (el.getAttribute('type') || el.tagName).toUpperCase();
|
|
879
|
+
return type in TEXT_CONTROLS;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Returns if the given element is a native HTML control.
|
|
883
|
+
* @param el The element.
|
|
884
|
+
* @return If the given element is a native HTML control.
|
|
885
|
+
*/
|
|
886
|
+
function isNativeHTMLControl(el) {
|
|
887
|
+
return el.tagName.toUpperCase() in NATIVE_HTML_CONTROLS;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Returns if the given element is natively activatable. Browsers emit click
|
|
891
|
+
* events for natively activatable elements, even when activated via keyboard.
|
|
892
|
+
* For these elements, we don't need to raise a11y click events.
|
|
893
|
+
* @param el The element.
|
|
894
|
+
* @return If the given element is a native HTML control.
|
|
895
|
+
*/
|
|
896
|
+
function isNativelyActivatable(el) {
|
|
897
|
+
return (el.tagName.toUpperCase() === 'BUTTON' ||
|
|
898
|
+
(!!el.type && el.type.toUpperCase() === 'FILE'));
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* HTML <input> types (not ARIA roles) which will auto-trigger a click event for
|
|
902
|
+
* the Space key, with side-effects. We will not call preventDefault if space is
|
|
903
|
+
* pressed, nor will we raise a11y click events. For all other elements, we can
|
|
904
|
+
* suppress the default event (which has no desired side-effects) and handle the
|
|
905
|
+
* keydown ourselves.
|
|
906
|
+
*/
|
|
907
|
+
const PROCESS_SPACE = {
|
|
908
|
+
'CHECKBOX': true,
|
|
909
|
+
'FILE': true,
|
|
910
|
+
'OPTION': true,
|
|
911
|
+
'RADIO': true,
|
|
912
|
+
};
|
|
913
|
+
/** TagNames and Input types for which to not process enter/space as click. */
|
|
914
|
+
const TEXT_CONTROLS = {
|
|
915
|
+
'COLOR': true,
|
|
916
|
+
'DATE': true,
|
|
917
|
+
'DATETIME': true,
|
|
918
|
+
'DATETIME-LOCAL': true,
|
|
919
|
+
'EMAIL': true,
|
|
920
|
+
'MONTH': true,
|
|
921
|
+
'NUMBER': true,
|
|
922
|
+
'PASSWORD': true,
|
|
923
|
+
'RANGE': true,
|
|
924
|
+
'SEARCH': true,
|
|
925
|
+
'TEL': true,
|
|
926
|
+
'TEXT': true,
|
|
927
|
+
'TEXTAREA': true,
|
|
928
|
+
'TIME': true,
|
|
929
|
+
'URL': true,
|
|
930
|
+
'WEEK': true,
|
|
931
|
+
};
|
|
932
|
+
/** TagNames that are native HTML controls. */
|
|
933
|
+
const NATIVE_HTML_CONTROLS = {
|
|
934
|
+
'A': true,
|
|
935
|
+
'AREA': true,
|
|
936
|
+
'BUTTON': true,
|
|
937
|
+
'DIALOG': true,
|
|
938
|
+
'IMG': true,
|
|
939
|
+
'INPUT': true,
|
|
940
|
+
'LINK': true,
|
|
941
|
+
'MENU': true,
|
|
942
|
+
'OPTGROUP': true,
|
|
943
|
+
'OPTION': true,
|
|
944
|
+
'PROGRESS': true,
|
|
945
|
+
'SELECT': true,
|
|
946
|
+
'TEXTAREA': true,
|
|
947
|
+
};
|
|
948
|
+
/** Exported for testing. */
|
|
949
|
+
const testing$1 = {
|
|
950
|
+
setIsMac(value) {
|
|
951
|
+
isMac = value;
|
|
952
|
+
},
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
/** Added for readability when accessing stable property names. */
|
|
956
|
+
function getEventType(eventInfo) {
|
|
957
|
+
return eventInfo.eventType;
|
|
958
|
+
}
|
|
959
|
+
/** Added for readability when accessing stable property names. */
|
|
960
|
+
function setEventType(eventInfo, eventType) {
|
|
961
|
+
eventInfo.eventType = eventType;
|
|
962
|
+
}
|
|
963
|
+
/** Added for readability when accessing stable property names. */
|
|
964
|
+
function getEvent(eventInfo) {
|
|
965
|
+
return eventInfo.event;
|
|
966
|
+
}
|
|
967
|
+
/** Added for readability when accessing stable property names. */
|
|
968
|
+
function setEvent(eventInfo, event) {
|
|
969
|
+
eventInfo.event = event;
|
|
970
|
+
}
|
|
971
|
+
/** Added for readability when accessing stable property names. */
|
|
972
|
+
function getTargetElement(eventInfo) {
|
|
973
|
+
return eventInfo.targetElement;
|
|
974
|
+
}
|
|
975
|
+
/** Added for readability when accessing stable property names. */
|
|
976
|
+
function setTargetElement(eventInfo, targetElement) {
|
|
977
|
+
eventInfo.targetElement = targetElement;
|
|
978
|
+
}
|
|
979
|
+
/** Added for readability when accessing stable property names. */
|
|
980
|
+
function getContainer(eventInfo) {
|
|
981
|
+
return eventInfo.eic;
|
|
982
|
+
}
|
|
983
|
+
/** Added for readability when accessing stable property names. */
|
|
984
|
+
function setContainer(eventInfo, container) {
|
|
985
|
+
eventInfo.eic = container;
|
|
986
|
+
}
|
|
987
|
+
/** Added for readability when accessing stable property names. */
|
|
988
|
+
function getTimestamp(eventInfo) {
|
|
989
|
+
return eventInfo.timeStamp;
|
|
990
|
+
}
|
|
991
|
+
/** Added for readability when accessing stable property names. */
|
|
992
|
+
function setTimestamp(eventInfo, timestamp) {
|
|
993
|
+
eventInfo.timeStamp = timestamp;
|
|
994
|
+
}
|
|
995
|
+
/** Added for readability when accessing stable property names. */
|
|
996
|
+
function getAction(eventInfo) {
|
|
997
|
+
return eventInfo.eia;
|
|
998
|
+
}
|
|
999
|
+
/** Added for readability when accessing stable property names. */
|
|
1000
|
+
function setAction(eventInfo, actionName, actionElement) {
|
|
1001
|
+
eventInfo.eia = [actionName, actionElement];
|
|
1002
|
+
}
|
|
1003
|
+
/** Added for readability when accessing stable property names. */
|
|
1004
|
+
function unsetAction(eventInfo) {
|
|
1005
|
+
eventInfo.eia = undefined;
|
|
1006
|
+
}
|
|
1007
|
+
/** Added for readability when accessing stable property names. */
|
|
1008
|
+
function getActionName(actionInfo) {
|
|
1009
|
+
return actionInfo[0];
|
|
1010
|
+
}
|
|
1011
|
+
/** Added for readability when accessing stable property names. */
|
|
1012
|
+
function getActionElement(actionInfo) {
|
|
1013
|
+
return actionInfo[1];
|
|
1014
|
+
}
|
|
1015
|
+
/** Added for readability when accessing stable property names. */
|
|
1016
|
+
function getIsReplay(eventInfo) {
|
|
1017
|
+
return eventInfo.eirp;
|
|
1018
|
+
}
|
|
1019
|
+
/** Added for readability when accessing stable property names. */
|
|
1020
|
+
function setIsReplay(eventInfo, replay) {
|
|
1021
|
+
eventInfo.eirp = replay;
|
|
1022
|
+
}
|
|
1023
|
+
/** Added for readability when accessing stable property names. */
|
|
1024
|
+
function getA11yClickKey(eventInfo) {
|
|
1025
|
+
return eventInfo.eiack;
|
|
1026
|
+
}
|
|
1027
|
+
/** Added for readability when accessing stable property names. */
|
|
1028
|
+
function setA11yClickKey(eventInfo, a11yClickKey) {
|
|
1029
|
+
eventInfo.eiack = a11yClickKey;
|
|
1030
|
+
}
|
|
1031
|
+
/** Clones an `EventInfo` */
|
|
1032
|
+
function cloneEventInfo(eventInfo) {
|
|
1033
|
+
return {
|
|
1034
|
+
eventType: eventInfo.eventType,
|
|
1035
|
+
event: eventInfo.event,
|
|
1036
|
+
targetElement: eventInfo.targetElement,
|
|
1037
|
+
eic: eventInfo.eic,
|
|
1038
|
+
eia: eventInfo.eia,
|
|
1039
|
+
timeStamp: eventInfo.timeStamp,
|
|
1040
|
+
eirp: eventInfo.eirp,
|
|
1041
|
+
eiack: eventInfo.eiack,
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Utility function for creating an `EventInfo`.
|
|
1046
|
+
*
|
|
1047
|
+
* This can be used from code-size sensitive compilation units, as taking
|
|
1048
|
+
* parameters vs. an `Object` literal reduces code size.
|
|
1049
|
+
*/
|
|
1050
|
+
function createEventInfoFromParameters(eventType, event, targetElement, container, timestamp, action, isReplay, a11yClickKey) {
|
|
1051
|
+
return {
|
|
1052
|
+
eventType,
|
|
1053
|
+
event,
|
|
1054
|
+
targetElement,
|
|
1055
|
+
eic: container,
|
|
1056
|
+
timeStamp: timestamp,
|
|
1057
|
+
eia: action,
|
|
1058
|
+
eirp: isReplay,
|
|
1059
|
+
eiack: a11yClickKey,
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Utility function for creating an `EventInfo`.
|
|
1064
|
+
*
|
|
1065
|
+
* This should be used in compilation units that are less sensitive to code
|
|
1066
|
+
* size.
|
|
1067
|
+
*/
|
|
1068
|
+
function createEventInfo({ eventType, event, targetElement, container, timestamp, action, isReplay, a11yClickKey, }) {
|
|
1069
|
+
return {
|
|
1070
|
+
eventType,
|
|
1071
|
+
event,
|
|
1072
|
+
targetElement,
|
|
1073
|
+
eic: container,
|
|
1074
|
+
timeStamp: timestamp,
|
|
1075
|
+
eia: action ? [action.name, action.element] : undefined,
|
|
1076
|
+
eirp: isReplay,
|
|
1077
|
+
eiack: a11yClickKey,
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Utility class around an `EventInfo`.
|
|
1082
|
+
*
|
|
1083
|
+
* This should be used in compilation units that are less sensitive to code
|
|
1084
|
+
* size.
|
|
1085
|
+
*/
|
|
1086
|
+
class EventInfoWrapper {
|
|
1087
|
+
constructor(eventInfo) {
|
|
1088
|
+
this.eventInfo = eventInfo;
|
|
1089
|
+
}
|
|
1090
|
+
getEventType() {
|
|
1091
|
+
return getEventType(this.eventInfo);
|
|
1092
|
+
}
|
|
1093
|
+
setEventType(eventType) {
|
|
1094
|
+
setEventType(this.eventInfo, eventType);
|
|
1095
|
+
}
|
|
1096
|
+
getEvent() {
|
|
1097
|
+
return getEvent(this.eventInfo);
|
|
1098
|
+
}
|
|
1099
|
+
setEvent(event) {
|
|
1100
|
+
setEvent(this.eventInfo, event);
|
|
1101
|
+
}
|
|
1102
|
+
getTargetElement() {
|
|
1103
|
+
return getTargetElement(this.eventInfo);
|
|
1104
|
+
}
|
|
1105
|
+
setTargetElement(targetElement) {
|
|
1106
|
+
setTargetElement(this.eventInfo, targetElement);
|
|
1107
|
+
}
|
|
1108
|
+
getContainer() {
|
|
1109
|
+
return getContainer(this.eventInfo);
|
|
1110
|
+
}
|
|
1111
|
+
setContainer(container) {
|
|
1112
|
+
setContainer(this.eventInfo, container);
|
|
1113
|
+
}
|
|
1114
|
+
getTimestamp() {
|
|
1115
|
+
return getTimestamp(this.eventInfo);
|
|
1116
|
+
}
|
|
1117
|
+
setTimestamp(timestamp) {
|
|
1118
|
+
setTimestamp(this.eventInfo, timestamp);
|
|
1119
|
+
}
|
|
1120
|
+
getAction() {
|
|
1121
|
+
const action = getAction(this.eventInfo);
|
|
1122
|
+
if (!action)
|
|
1123
|
+
return undefined;
|
|
1124
|
+
return {
|
|
1125
|
+
name: action[0],
|
|
1126
|
+
element: action[1],
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
setAction(action) {
|
|
1130
|
+
if (!action) {
|
|
1131
|
+
unsetAction(this.eventInfo);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
setAction(this.eventInfo, action.name, action.element);
|
|
1135
|
+
}
|
|
1136
|
+
getIsReplay() {
|
|
1137
|
+
return getIsReplay(this.eventInfo);
|
|
1138
|
+
}
|
|
1139
|
+
setIsReplay(replay) {
|
|
1140
|
+
setIsReplay(this.eventInfo, replay);
|
|
1141
|
+
}
|
|
1142
|
+
clone() {
|
|
1143
|
+
return new EventInfoWrapper(cloneEventInfo(this.eventInfo));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Create a custom event with the specified data.
|
|
1149
|
+
* @param type The type of the action, e.g., 'submit'.
|
|
1150
|
+
* @param data An optional data payload.
|
|
1151
|
+
* @param triggeringEvent The event that triggers this custom event. This can be
|
|
1152
|
+
* accessed from the custom event's action flow like so:
|
|
1153
|
+
* actionFlow.event().detail.triggeringEvent.
|
|
1154
|
+
* @return The new custom event.
|
|
1155
|
+
*/
|
|
1156
|
+
function createCustomEvent(type, data, triggeringEvent) {
|
|
1157
|
+
let event;
|
|
1158
|
+
const unrenamedDetail = {
|
|
1159
|
+
'_type': type,
|
|
1160
|
+
};
|
|
1161
|
+
const renamedDetail = {
|
|
1162
|
+
type,
|
|
1163
|
+
data,
|
|
1164
|
+
triggeringEvent,
|
|
1165
|
+
};
|
|
1166
|
+
const detail = { ...unrenamedDetail, ...renamedDetail };
|
|
1167
|
+
try {
|
|
1168
|
+
// We don't use the CustomEvent constructor directly since it isn't
|
|
1169
|
+
// supported in IE 9 or 10 and initCustomEvent below works just fine.
|
|
1170
|
+
event = document.createEvent('CustomEvent');
|
|
1171
|
+
event.initCustomEvent(EventType.CUSTOM, true, false, detail);
|
|
1172
|
+
}
|
|
1173
|
+
catch (e) {
|
|
1174
|
+
// If custom events aren't supported, fall back to custom-named HTMLEvent.
|
|
1175
|
+
// Fallback used by Android Gingerbread, FF4-5.
|
|
1176
|
+
// Hack to emulate `CustomEvent`, `HTMLEvents` doesn't satisfy `CustomEvent`
|
|
1177
|
+
// type.
|
|
1178
|
+
// tslint:disable-next-line:no-any
|
|
1179
|
+
event = document.createEvent('HTMLEvents');
|
|
1180
|
+
event.initEvent(EventType.CUSTOM, true, false);
|
|
1181
|
+
// Hack to emulate `CustomEvent`, `detail` is readonly on `CustomEvent`.
|
|
1182
|
+
// tslint:disable-next-line:no-any
|
|
1183
|
+
event['detail'] = detail;
|
|
1184
|
+
}
|
|
1185
|
+
return event;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Fires a custom event with an optional payload. Only intended to be consumed
|
|
1189
|
+
* by jsaction itself. Supported in Firefox 6+, IE 9+, and all Chrome versions.
|
|
1190
|
+
*
|
|
1191
|
+
* @param target The target element.
|
|
1192
|
+
* @param type The type of the action, e.g., 'submit'.
|
|
1193
|
+
* @param data An optional data payload.
|
|
1194
|
+
* @param triggeringEvent An optional data for the Event triggered this custom
|
|
1195
|
+
* event.
|
|
1196
|
+
*/
|
|
1197
|
+
function fireCustomEvent(target, type, data, triggeringEvent) {
|
|
1198
|
+
const event = createCustomEvent(type, data, triggeringEvent);
|
|
1199
|
+
target.dispatchEvent(event);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* @fileoverview Functions for replaying events by the jsaction
|
|
1204
|
+
* Dispatcher.
|
|
1205
|
+
* All ts-ignores in this file are due to APIs that are no longer in the browser.
|
|
1206
|
+
*/
|
|
1207
|
+
/**
|
|
1208
|
+
* Replays an event.
|
|
1209
|
+
*/
|
|
1210
|
+
function replayEvent(event, targetElement, eventType) {
|
|
1211
|
+
triggerEvent(targetElement, createEvent(event, eventType));
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Checks if a given event was triggered by the keyboard.
|
|
1215
|
+
* @param eventType The event type.
|
|
1216
|
+
* @return Whether it's a keyboard event.
|
|
1217
|
+
*/
|
|
1218
|
+
function isKeyboardEvent(eventType) {
|
|
1219
|
+
return (eventType === EventType.KEYPRESS ||
|
|
1220
|
+
eventType === EventType.KEYDOWN ||
|
|
1221
|
+
eventType === EventType.KEYUP);
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Checks if a given event was triggered by the mouse.
|
|
1225
|
+
* @param eventType The event type.
|
|
1226
|
+
* @return Whether it's a mouse event.
|
|
1227
|
+
*/
|
|
1228
|
+
function isMouseEvent(eventType) {
|
|
1229
|
+
// TODO: Verify if Drag events should be bound here.
|
|
1230
|
+
return (eventType === EventType.CLICK ||
|
|
1231
|
+
eventType === EventType.DBLCLICK ||
|
|
1232
|
+
eventType === EventType.MOUSEDOWN ||
|
|
1233
|
+
eventType === EventType.MOUSEOVER ||
|
|
1234
|
+
eventType === EventType.MOUSEOUT ||
|
|
1235
|
+
eventType === EventType.MOUSEMOVE);
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Checks if a given event is a general UI event.
|
|
1239
|
+
* @param eventType The event type.
|
|
1240
|
+
* @return Whether it's a focus event.
|
|
1241
|
+
*/
|
|
1242
|
+
function isUiEvent(eventType) {
|
|
1243
|
+
// Almost nobody supports the W3C method of creating FocusEvents.
|
|
1244
|
+
// For now, we're going to use the UIEvent as a super-interface.
|
|
1245
|
+
return (eventType === EventType.FOCUS ||
|
|
1246
|
+
eventType === EventType.BLUR ||
|
|
1247
|
+
eventType === EventType.FOCUSIN ||
|
|
1248
|
+
eventType === EventType.FOCUSOUT ||
|
|
1249
|
+
eventType === EventType.SCROLL);
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Create a whitespace-delineated list of modifier keys that should be
|
|
1253
|
+
* considered to be active on the event's key. See details at
|
|
1254
|
+
* https://developer.mozilla.org/en/DOM/KeyboardEvent.
|
|
1255
|
+
* @param alt Alt pressed.
|
|
1256
|
+
* @param ctrl Control pressed.
|
|
1257
|
+
* @param meta Command pressed (OSX only).
|
|
1258
|
+
* @param shift Shift pressed.
|
|
1259
|
+
* @return The constructed modifier keys string.
|
|
1260
|
+
*/
|
|
1261
|
+
function createKeyboardModifiersList(alt, ctrl, meta, shift) {
|
|
1262
|
+
const keys = [];
|
|
1263
|
+
if (alt) {
|
|
1264
|
+
keys.push('Alt');
|
|
1265
|
+
}
|
|
1266
|
+
if (ctrl) {
|
|
1267
|
+
keys.push('Control');
|
|
1268
|
+
}
|
|
1269
|
+
if (meta) {
|
|
1270
|
+
keys.push('Meta');
|
|
1271
|
+
}
|
|
1272
|
+
if (shift) {
|
|
1273
|
+
keys.push('Shift');
|
|
1274
|
+
}
|
|
1275
|
+
return keys.join(' ');
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Creates a UI event object for replaying through the DOM.
|
|
1279
|
+
* @param original The event to create a new event from.
|
|
1280
|
+
* @param opt_eventType The type this event is being handled as by jsaction.
|
|
1281
|
+
* e.g. blur events are handled as focusout
|
|
1282
|
+
* @return The event object.
|
|
1283
|
+
*/
|
|
1284
|
+
function createUiEvent(original, opt_eventType) {
|
|
1285
|
+
let event;
|
|
1286
|
+
if (document.createEvent) {
|
|
1287
|
+
const originalUiEvent = original;
|
|
1288
|
+
// Event creation as per W3C event model specification. This codepath
|
|
1289
|
+
// is used by most non-IE browsers and also by IE 9 and later.
|
|
1290
|
+
event = document.createEvent('UIEvent');
|
|
1291
|
+
// On IE and Opera < 12, we must provide non-undefined values to
|
|
1292
|
+
// initEvent, otherwise it will fail.
|
|
1293
|
+
event.initUIEvent(opt_eventType || originalUiEvent.type, originalUiEvent.bubbles !== undefined ? originalUiEvent.bubbles : true, originalUiEvent.cancelable || false, originalUiEvent.view || window, original.detail || 0);
|
|
1294
|
+
// detail
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
// Older versions of IE (up to version 8) do not support the
|
|
1298
|
+
// W3C event model. Use the IE specific function instead.
|
|
1299
|
+
// Suppressing errors for ts-migration.
|
|
1300
|
+
// TS2339: Property 'createEventObject' does not exist on type 'Document'.
|
|
1301
|
+
// @ts-ignore
|
|
1302
|
+
event = document.createEventObject();
|
|
1303
|
+
event.type = opt_eventType || original.type;
|
|
1304
|
+
event.bubbles = original.bubbles !== undefined ? original.bubbles : true;
|
|
1305
|
+
event.cancelable = original.cancelable || false;
|
|
1306
|
+
event.view = original.view || window;
|
|
1307
|
+
event.detail = original.detail || 0;
|
|
1308
|
+
}
|
|
1309
|
+
// Some focus events also have a nullable relatedTarget value which isn't
|
|
1310
|
+
// directly supported in the initUIEvent() method.
|
|
1311
|
+
event.relatedTarget = original.relatedTarget || null;
|
|
1312
|
+
event.originalTimestamp = original.timeStamp;
|
|
1313
|
+
return event;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Creates a keyboard event object for replaying through the DOM.
|
|
1317
|
+
* @param original The event to create a new event from.
|
|
1318
|
+
* @param opt_eventType The type this event is being handled as by jsaction.
|
|
1319
|
+
* E.g. a keypress is handled as click in some cases.
|
|
1320
|
+
* @return The event object.
|
|
1321
|
+
* @suppress {strictMissingProperties} Two definitions of initKeyboardEvent.
|
|
1322
|
+
*/
|
|
1323
|
+
function createKeyboardEvent(original, opt_eventType) {
|
|
1324
|
+
let event;
|
|
1325
|
+
const keyboardEvent = original;
|
|
1326
|
+
if (document.createEvent) {
|
|
1327
|
+
// Event creation as per W3C event model specification. This codepath
|
|
1328
|
+
// is used by most non-IE browsers and also by IE 9 and later.
|
|
1329
|
+
event = document.createEvent('KeyboardEvent');
|
|
1330
|
+
if (event.initKeyboardEvent) {
|
|
1331
|
+
if (isIe) {
|
|
1332
|
+
// IE9+
|
|
1333
|
+
// https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ff975945(v=vs.85)
|
|
1334
|
+
const modifiers = createKeyboardModifiersList(keyboardEvent.altKey, keyboardEvent.ctrlKey, keyboardEvent.metaKey, keyboardEvent.shiftKey);
|
|
1335
|
+
event.initKeyboardEvent(opt_eventType || keyboardEvent.type, true, true, window, keyboardEvent.key, keyboardEvent.location,
|
|
1336
|
+
// Suppressing errors for ts-migration.
|
|
1337
|
+
// TS2345: Argument of type 'string' is not assignable to
|
|
1338
|
+
// parameter of type 'boolean | undefined'.
|
|
1339
|
+
// @ts-ignore
|
|
1340
|
+
modifiers, keyboardEvent.repeat,
|
|
1341
|
+
// @ts-ignore This doesn't exist
|
|
1342
|
+
keyboardEvent.locale);
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
// W3C DOM Level 3 Events model.
|
|
1346
|
+
// https://www.w3.org/TR/uievents/#idl-interface-KeyboardEvent-initializers
|
|
1347
|
+
event.initKeyboardEvent(opt_eventType || original.type, true, true, window, keyboardEvent.key, keyboardEvent.location, keyboardEvent.ctrlKey, keyboardEvent.altKey, keyboardEvent.shiftKey, keyboardEvent.metaKey);
|
|
1348
|
+
Object.defineProperty(event, 'repeat', {
|
|
1349
|
+
get: () => original.repeat,
|
|
1350
|
+
enumerable: true,
|
|
1351
|
+
});
|
|
1352
|
+
// Add missing 'locale' which is not part of the spec.
|
|
1353
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=168971
|
|
1354
|
+
Object.defineProperty(event, 'locale', {
|
|
1355
|
+
// Suppressing errors for ts-migration.
|
|
1356
|
+
// TS2339: Property 'locale' does not exist on type 'Event'.
|
|
1357
|
+
// @ts-ignore
|
|
1358
|
+
get: () => original.locale,
|
|
1359
|
+
enumerable: true,
|
|
1360
|
+
});
|
|
1361
|
+
// Apple WebKit has a non-standard altGraphKey that is not implemented
|
|
1362
|
+
// here.
|
|
1363
|
+
// https://developer.apple.com/documentation/webkitjs/keyboardevent/1633753-initkeyboardevent
|
|
1364
|
+
}
|
|
1365
|
+
// Blink and Webkit had a bug that causes the `charCode`, `keyCode`, and
|
|
1366
|
+
// `which` properties to always be unset when synthesizing a keyboard
|
|
1367
|
+
// event. Details at: https://bugs.webkit.org/show_bug.cgi?id=16735. With
|
|
1368
|
+
// these properties being deprecated, the bug has evolved into affecting
|
|
1369
|
+
// the `key` property. We work around it by redefining the `key` and
|
|
1370
|
+
// deprecated properties; a simple assignment here would fail because the
|
|
1371
|
+
// native properties are readonly.
|
|
1372
|
+
if (isWebKit) {
|
|
1373
|
+
if (keyboardEvent.key && event.key === '') {
|
|
1374
|
+
Object.defineProperty(event, 'key', {
|
|
1375
|
+
get: () => keyboardEvent.key,
|
|
1376
|
+
enumerable: true,
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
// Re-implement the deprecated `charCode`, `keyCode` and `which` which
|
|
1381
|
+
// are also an issue on IE9+.
|
|
1382
|
+
if (isWebKit || isIe || isGecko) {
|
|
1383
|
+
Object.defineProperty(event, 'charCode', {
|
|
1384
|
+
get: () => original.charCode,
|
|
1385
|
+
enumerable: true,
|
|
1386
|
+
});
|
|
1387
|
+
const keyCodeGetter = () => original.keyCode;
|
|
1388
|
+
Object.defineProperty(event, 'keyCode', {
|
|
1389
|
+
get: keyCodeGetter,
|
|
1390
|
+
enumerable: true,
|
|
1391
|
+
});
|
|
1392
|
+
Object.defineProperty(event, 'which', {
|
|
1393
|
+
get: keyCodeGetter,
|
|
1394
|
+
enumerable: true,
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
else {
|
|
1399
|
+
// Gecko only supports an older/deprecated version from DOM Level 2. See
|
|
1400
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyEvent
|
|
1401
|
+
// for details.
|
|
1402
|
+
// @ts-ignore ditto
|
|
1403
|
+
event.initKeyEvent(opt_eventType || original.type, true, true, window,
|
|
1404
|
+
// Suppressing errors for ts-migration.
|
|
1405
|
+
// TS2339: Property 'ctrlKey' does not exist on type 'Event'.
|
|
1406
|
+
// @ts-ignore
|
|
1407
|
+
original.ctrlKey,
|
|
1408
|
+
// Suppressing errors for ts-migration.
|
|
1409
|
+
// TS2339: Property 'altKey' does not exist on type 'Event'.
|
|
1410
|
+
// @ts-ignore
|
|
1411
|
+
original.altKey,
|
|
1412
|
+
// Suppressing errors for ts-migration.
|
|
1413
|
+
// TS2339: Property 'shiftKey' does not exist on type 'Event'.
|
|
1414
|
+
// @ts-ignore
|
|
1415
|
+
original.shiftKey,
|
|
1416
|
+
// Suppressing errors for ts-migration.
|
|
1417
|
+
// TS2339: Property 'metaKey' does not exist on type 'Event'.
|
|
1418
|
+
// @ts-ignore
|
|
1419
|
+
original.metaKey,
|
|
1420
|
+
// Suppressing errors for ts-migration.
|
|
1421
|
+
// TS2339: Property 'keyCode' does not exist on type 'Event'.
|
|
1422
|
+
// @ts-ignore
|
|
1423
|
+
original.keyCode,
|
|
1424
|
+
// Suppressing errors for ts-migration.
|
|
1425
|
+
// TS2339: Property 'charCode' does not exist on type 'Event'.
|
|
1426
|
+
// @ts-ignore
|
|
1427
|
+
original.charCode);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
// Older versions of IE (up to version 8) do not support the
|
|
1432
|
+
// W3C event model. Use the IE specific function instead.
|
|
1433
|
+
// Suppressing errors for ts-migration.
|
|
1434
|
+
// @ts-ignore
|
|
1435
|
+
event = document.createEventObject();
|
|
1436
|
+
event.type = opt_eventType || original.type;
|
|
1437
|
+
const originalKeyboardEvent = original;
|
|
1438
|
+
event.repeat = originalKeyboardEvent.repeat;
|
|
1439
|
+
event.ctrlKey = originalKeyboardEvent.ctrlKey;
|
|
1440
|
+
event.altKey = originalKeyboardEvent.altKey;
|
|
1441
|
+
event.shiftKey = originalKeyboardEvent.shiftKey;
|
|
1442
|
+
event.metaKey = originalKeyboardEvent.metaKey;
|
|
1443
|
+
event.key = originalKeyboardEvent.key;
|
|
1444
|
+
event.keyCode = originalKeyboardEvent.keyCode;
|
|
1445
|
+
event.charCode = originalKeyboardEvent.charCode;
|
|
1446
|
+
}
|
|
1447
|
+
event.originalTimestamp = original.timeStamp;
|
|
1448
|
+
return event;
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Creates a mouse event object for replaying through the DOM.
|
|
1452
|
+
* @param original The event to create a new event from.
|
|
1453
|
+
* @param opt_eventType The type this event is being handled as by jsaction.
|
|
1454
|
+
* E.g. a keypress is handled as click in some cases.
|
|
1455
|
+
* @return The event object.
|
|
1456
|
+
*/
|
|
1457
|
+
function createMouseEvent(original, opt_eventType) {
|
|
1458
|
+
let event;
|
|
1459
|
+
const originalMouseEvent = original;
|
|
1460
|
+
if (document.createEvent) {
|
|
1461
|
+
// Event creation as per W3C event model specification. This codepath
|
|
1462
|
+
// is used by most non-IE browsers and also by IE 9 and later.
|
|
1463
|
+
event = document.createEvent('MouseEvent');
|
|
1464
|
+
// On IE and Opera < 12, we must provide non-undefined values to
|
|
1465
|
+
// initMouseEvent, otherwise it will fail.
|
|
1466
|
+
event.initMouseEvent(opt_eventType || original.type, true, // canBubble
|
|
1467
|
+
true, // cancelable
|
|
1468
|
+
window, original.detail || 1, originalMouseEvent.screenX || 0, originalMouseEvent.screenY || 0, originalMouseEvent.clientX || 0, originalMouseEvent.clientY || 0, originalMouseEvent.ctrlKey || false, originalMouseEvent.altKey || false, originalMouseEvent.shiftKey || false, originalMouseEvent.metaKey || false, originalMouseEvent.button || 0, originalMouseEvent.relatedTarget || null);
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
// Older versions of IE (up to version 8) do not support the
|
|
1472
|
+
// W3C event model. Use the IE specific function instead.
|
|
1473
|
+
// @ts-ignore
|
|
1474
|
+
event = document.createEventObject();
|
|
1475
|
+
event.type = opt_eventType || original.type;
|
|
1476
|
+
event.clientX = originalMouseEvent.clientX;
|
|
1477
|
+
event.clientY = originalMouseEvent.clientY;
|
|
1478
|
+
event.button = originalMouseEvent.button;
|
|
1479
|
+
event.detail = original.detail;
|
|
1480
|
+
event.ctrlKey = originalMouseEvent.ctrlKey;
|
|
1481
|
+
event.altKey = originalMouseEvent.altKey;
|
|
1482
|
+
event.shiftKey = originalMouseEvent.shiftKey;
|
|
1483
|
+
event.metaKey = originalMouseEvent.metaKey;
|
|
1484
|
+
}
|
|
1485
|
+
event.originalTimestamp = original.timeStamp;
|
|
1486
|
+
return event;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Creates a generic event object for replaying through the DOM.
|
|
1490
|
+
* @param original The event to create a new event from.
|
|
1491
|
+
* @param opt_eventType The type this event is being handled as by jsaction.
|
|
1492
|
+
* E.g. a keypress is handled as click in some cases.
|
|
1493
|
+
* @return The event object.
|
|
1494
|
+
*/
|
|
1495
|
+
function createGenericEvent(original, opt_eventType) {
|
|
1496
|
+
let event;
|
|
1497
|
+
if (document.createEvent) {
|
|
1498
|
+
// Event creation as per W3C event model specification. This codepath
|
|
1499
|
+
// is used by most non-IE browsers and also by IE 9 and later.
|
|
1500
|
+
event = document.createEvent('Event');
|
|
1501
|
+
event.initEvent(opt_eventType || original.type, true, true);
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
// Older versions of IE (up to version 8) do not support the
|
|
1505
|
+
// W3C event model. Use the IE specific function instead.
|
|
1506
|
+
// Suppressing errors for ts-migration.
|
|
1507
|
+
// TS2339: Property 'createEventObject' does not exist on type 'Document'.
|
|
1508
|
+
// @ts-ignore
|
|
1509
|
+
event = document.createEventObject();
|
|
1510
|
+
event.type = opt_eventType || original.type;
|
|
1511
|
+
}
|
|
1512
|
+
event.originalTimestamp = original.timeStamp;
|
|
1513
|
+
return event;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Creates an event object for replaying through the DOM.
|
|
1517
|
+
* NOTE: This function is visible just for testing. Please don't use
|
|
1518
|
+
* it outside JsAction internal testing.
|
|
1519
|
+
* TODO: Add support for FocusEvent and WheelEvent.
|
|
1520
|
+
* @param base The event to create a new event from.
|
|
1521
|
+
* @param opt_eventType The type this event is being handled as by jsaction.
|
|
1522
|
+
* E.g. a keypress is handled as click in some cases.
|
|
1523
|
+
* @return The event object.
|
|
1524
|
+
*/
|
|
1525
|
+
function createEvent(base, opt_eventType) {
|
|
1526
|
+
const original = base;
|
|
1527
|
+
let event;
|
|
1528
|
+
let eventType;
|
|
1529
|
+
if (original.type === EventType.CUSTOM) {
|
|
1530
|
+
eventType = EventType.CUSTOM;
|
|
1531
|
+
}
|
|
1532
|
+
else {
|
|
1533
|
+
eventType = opt_eventType || original.type;
|
|
1534
|
+
}
|
|
1535
|
+
if (isKeyboardEvent(eventType)) {
|
|
1536
|
+
event = createKeyboardEvent(original, opt_eventType);
|
|
1537
|
+
}
|
|
1538
|
+
else if (isMouseEvent(eventType)) {
|
|
1539
|
+
event = createMouseEvent(original, opt_eventType);
|
|
1540
|
+
}
|
|
1541
|
+
else if (isUiEvent(eventType)) {
|
|
1542
|
+
event = createUiEvent(original, opt_eventType);
|
|
1543
|
+
}
|
|
1544
|
+
else if (eventType === EventType.CUSTOM) {
|
|
1545
|
+
event = createCustomEvent(opt_eventType, original['detail']['data'], original['detail']['triggeringEvent']);
|
|
1546
|
+
event.originalTimestamp = original.timeStamp;
|
|
1547
|
+
}
|
|
1548
|
+
else {
|
|
1549
|
+
// This ensures we don't send an undefined event object to the replayer.
|
|
1550
|
+
event = createGenericEvent(original, opt_eventType);
|
|
1551
|
+
}
|
|
1552
|
+
return event;
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Sends an event for replay to the DOM.
|
|
1556
|
+
* @param target The target for the event.
|
|
1557
|
+
* @param event The event object.
|
|
1558
|
+
* @return The return value of the event replay, i.e., whether preventDefault()
|
|
1559
|
+
* was called on it.
|
|
1560
|
+
*/
|
|
1561
|
+
function triggerEvent(target, event) {
|
|
1562
|
+
if (target.dispatchEvent) {
|
|
1563
|
+
return target.dispatchEvent(event);
|
|
1564
|
+
}
|
|
1565
|
+
else {
|
|
1566
|
+
// Suppressing errors for ts-migration.
|
|
1567
|
+
// TS2339: Property 'fireEvent' does not exist on type 'Element'.
|
|
1568
|
+
// @ts-ignore
|
|
1569
|
+
return target.fireEvent('on' + event.type, event);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
/** Do not use outiside of testing. */
|
|
1573
|
+
const testing = {
|
|
1574
|
+
createKeyboardModifiersList,
|
|
1575
|
+
createGenericEvent,
|
|
1576
|
+
isKeyboardEvent,
|
|
1577
|
+
isMouseEvent,
|
|
1578
|
+
isUiEvent,
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1581
|
+
/**
|
|
1582
|
+
* @fileoverview An enum to control who can call certain jsaction APIs.
|
|
1583
|
+
*/
|
|
1584
|
+
var Restriction;
|
|
1585
|
+
(function (Restriction) {
|
|
1586
|
+
Restriction[Restriction["I_AM_THE_JSACTION_FRAMEWORK"] = 1] = "I_AM_THE_JSACTION_FRAMEWORK";
|
|
1587
|
+
})(Restriction || (Restriction = {}));
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* Receives a DOM event, determines the jsaction associated with the source
|
|
1591
|
+
* element of the DOM event, and invokes the handler associated with the
|
|
1592
|
+
* jsaction.
|
|
1593
|
+
*/
|
|
1594
|
+
class BaseDispatcher {
|
|
1595
|
+
/**
|
|
1596
|
+
* Options are:
|
|
1597
|
+
* 1. `eventReplayer`: When the event contract dispatches replay events
|
|
1598
|
+
* to the Dispatcher, the Dispatcher collects them and in the next tick
|
|
1599
|
+
* dispatches them to the `eventReplayer`.
|
|
1600
|
+
* @param dispatchDelegate A function that should handle dispatching an `EventInfoWrapper` to handlers.
|
|
1601
|
+
*/
|
|
1602
|
+
constructor(dispatchDelegate, { eventReplayer = undefined } = {}) {
|
|
1603
|
+
this.dispatchDelegate = dispatchDelegate;
|
|
1604
|
+
/** The queue of events. */
|
|
1605
|
+
this.queuedEventInfoWrappers = [];
|
|
1606
|
+
/** Whether the event replay is scheduled. */
|
|
1607
|
+
this.eventReplayScheduled = false;
|
|
1608
|
+
this.eventReplayer = eventReplayer;
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Receives an event or the event queue from the EventContract. The event
|
|
1612
|
+
* queue is copied and it attempts to replay.
|
|
1613
|
+
* If event info is passed in it looks for an action handler that can handle
|
|
1614
|
+
* the given event. If there is no handler registered queues the event and
|
|
1615
|
+
* checks if a loader is registered for the given namespace. If so, calls it.
|
|
1616
|
+
*
|
|
1617
|
+
* Alternatively, if in global dispatch mode, calls all registered global
|
|
1618
|
+
* handlers for the appropriate event type.
|
|
1619
|
+
*
|
|
1620
|
+
* The three functionalities of this call are deliberately not split into
|
|
1621
|
+
* three methods (and then declared as an abstract interface), because the
|
|
1622
|
+
* interface is used by EventContract, which lives in a different jsbinary.
|
|
1623
|
+
* Therefore the interface between the three is defined entirely in terms that
|
|
1624
|
+
* are invariant under jscompiler processing (Function and Array, as opposed
|
|
1625
|
+
* to a custom type with method names).
|
|
1626
|
+
*
|
|
1627
|
+
* @param eventInfo The info for the event that triggered this call or the
|
|
1628
|
+
* queue of events from EventContract.
|
|
1629
|
+
* @param isGlobalDispatch If true, dispatches a global event instead of a
|
|
1630
|
+
* regular jsaction handler.
|
|
1631
|
+
* @return An `EventInfo` for the `EventContract` to handle again if the
|
|
1632
|
+
* `Dispatcher` tried to resolve an a11y event as a click but failed.
|
|
1633
|
+
*/
|
|
1634
|
+
dispatch(eventInfo, isGlobalDispatch) {
|
|
1635
|
+
const eventInfoWrapper = new EventInfoWrapper(eventInfo);
|
|
1636
|
+
if (eventInfoWrapper.getIsReplay()) {
|
|
1637
|
+
if (isGlobalDispatch || !this.eventReplayer) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const resolved = resolveA11yEvent(eventInfoWrapper, isGlobalDispatch);
|
|
1641
|
+
if (!resolved) {
|
|
1642
|
+
// Send the event back through the `EventContract` by dispatching to
|
|
1643
|
+
// the browser.
|
|
1644
|
+
replayEvent(eventInfoWrapper.getEvent(), eventInfoWrapper.getTargetElement(), eventInfoWrapper.getEventType());
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
this.queueEventInfoWrapper(eventInfoWrapper);
|
|
1648
|
+
this.scheduleEventReplay();
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const resolved = resolveA11yEvent(eventInfoWrapper, isGlobalDispatch);
|
|
1652
|
+
if (!resolved) {
|
|
1653
|
+
// Reset action information.
|
|
1654
|
+
eventInfoWrapper.setAction(undefined);
|
|
1655
|
+
return eventInfoWrapper.eventInfo;
|
|
1656
|
+
}
|
|
1657
|
+
this.dispatchDelegate(eventInfoWrapper, isGlobalDispatch);
|
|
1658
|
+
}
|
|
1659
|
+
/** Queue an `EventInfoWrapper` for replay. */
|
|
1660
|
+
queueEventInfoWrapper(eventInfoWrapper) {
|
|
1661
|
+
this.queuedEventInfoWrappers.push(eventInfoWrapper);
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Replays queued events, if any. The replaying will happen in its own
|
|
1665
|
+
* stack once the current flow cedes control. This is done to mimic
|
|
1666
|
+
* browser event handling.
|
|
1667
|
+
*/
|
|
1668
|
+
scheduleEventReplay() {
|
|
1669
|
+
if (this.eventReplayScheduled ||
|
|
1670
|
+
!this.eventReplayer ||
|
|
1671
|
+
this.queuedEventInfoWrappers.length === 0) {
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
this.eventReplayScheduled = true;
|
|
1675
|
+
Promise.resolve().then(() => {
|
|
1676
|
+
this.eventReplayScheduled = false;
|
|
1677
|
+
this.eventReplayer(this.queuedEventInfoWrappers);
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* If a 'MAYBE_CLICK_EVENT_TYPE' event was dispatched, updates the eventType
|
|
1683
|
+
* to either click or keydown based on whether the keydown action can be
|
|
1684
|
+
* treated as a click. For MAYBE_CLICK_EVENT_TYPE events that are just
|
|
1685
|
+
* keydowns, we set flags on the event object so that the event contract
|
|
1686
|
+
* does't try to dispatch it as a MAYBE_CLICK_EVENT_TYPE again.
|
|
1687
|
+
*
|
|
1688
|
+
* @param isGlobalDispatch Whether the eventInfo is meant to be dispatched to
|
|
1689
|
+
* the global handlers.
|
|
1690
|
+
* @return Returns false if the a11y event could not be resolved and should
|
|
1691
|
+
* be re-dispatched.
|
|
1692
|
+
*/
|
|
1693
|
+
function resolveA11yEvent(eventInfoWrapper, isGlobalDispatch = false) {
|
|
1694
|
+
if (eventInfoWrapper.getEventType() !== Attribute$1.MAYBE_CLICK_EVENT_TYPE) {
|
|
1695
|
+
return true;
|
|
1696
|
+
}
|
|
1697
|
+
if (isA11yClickEvent(eventInfoWrapper, isGlobalDispatch)) {
|
|
1698
|
+
if (shouldPreventDefault(eventInfoWrapper)) {
|
|
1699
|
+
preventDefault(eventInfoWrapper.getEvent());
|
|
1700
|
+
}
|
|
1701
|
+
// If the keydown event can be treated as a click, we change the eventType
|
|
1702
|
+
// to 'click' so that the dispatcher can retrieve the right handler for
|
|
1703
|
+
// it. Even though EventInfo['action'] corresponds to the click action,
|
|
1704
|
+
// the global handler and any custom 'getHandler' implementations may rely
|
|
1705
|
+
// on the eventType instead.
|
|
1706
|
+
eventInfoWrapper.setEventType(EventType.CLICK);
|
|
1707
|
+
}
|
|
1708
|
+
else {
|
|
1709
|
+
// Otherwise, if the keydown can't be treated as a click, we need to
|
|
1710
|
+
// retrigger it because now we need to look for 'keydown' actions instead.
|
|
1711
|
+
eventInfoWrapper.setEventType(EventType.KEYDOWN);
|
|
1712
|
+
if (!isGlobalDispatch) {
|
|
1713
|
+
// This prevents the event contract from setting the
|
|
1714
|
+
// AccessibilityAttribute.MAYBE_CLICK_EVENT_TYPE type for Keydown
|
|
1715
|
+
// events.
|
|
1716
|
+
eventInfoWrapper.getEvent()[Attribute$1.SKIP_A11Y_CHECK] = true;
|
|
1717
|
+
// Since globally dispatched events will get handled by the dispatcher,
|
|
1718
|
+
// don't have the event contract dispatch it again.
|
|
1719
|
+
eventInfoWrapper.getEvent()[Attribute$1.SKIP_GLOBAL_DISPATCH] = true;
|
|
1720
|
+
return false;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return true;
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Returns true if the default action for this event should be prevented
|
|
1727
|
+
* before the event handler is envoked.
|
|
1728
|
+
*/
|
|
1729
|
+
function shouldPreventDefault(eventInfoWrapper) {
|
|
1730
|
+
const actionElement = eventInfoWrapper.getAction()?.element;
|
|
1731
|
+
// For parity with no-a11y-support behavior.
|
|
1732
|
+
if (!actionElement) {
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
// Prevent scrolling if the Space key was pressed
|
|
1736
|
+
if (isSpaceKeyEvent(eventInfoWrapper.getEvent())) {
|
|
1737
|
+
return true;
|
|
1738
|
+
}
|
|
1739
|
+
// or prevent the browser's default action for native HTML controls.
|
|
1740
|
+
if (shouldCallPreventDefaultOnNativeHtmlControl(eventInfoWrapper.getEvent())) {
|
|
1741
|
+
return true;
|
|
1742
|
+
}
|
|
1743
|
+
// Prevent browser from following <a> node links if a jsaction is present
|
|
1744
|
+
// and we are dispatching the action now. Note that the targetElement may be
|
|
1745
|
+
// a child of an anchor that has a jsaction attached. For that reason, we
|
|
1746
|
+
// need to check the actionElement rather than the targetElement.
|
|
1747
|
+
if (actionElement.tagName === 'A') {
|
|
1748
|
+
return true;
|
|
1749
|
+
}
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Returns true if the given key event can be treated as a 'click'.
|
|
1754
|
+
*
|
|
1755
|
+
* @param isGlobalDispatch Whether the eventInfo is meant to be dispatched to
|
|
1756
|
+
* the global handlers.
|
|
1757
|
+
*/
|
|
1758
|
+
function isA11yClickEvent(eventInfoWrapper, isGlobalDispatch) {
|
|
1759
|
+
return ((isGlobalDispatch || eventInfoWrapper.getAction() !== undefined) &&
|
|
1760
|
+
isActionKeyEvent(eventInfoWrapper.getEvent()));
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Registers deferred functionality for an EventContract and a Jsaction
|
|
1764
|
+
* Dispatcher.
|
|
1765
|
+
*/
|
|
1766
|
+
function registerDispatcher$1(eventContract, dispatcher) {
|
|
1767
|
+
eventContract.ecrd((eventInfo, globalDispatch) => {
|
|
1768
|
+
return dispatcher.dispatch(eventInfo, globalDispatch);
|
|
1769
|
+
}, Restriction.I_AM_THE_JSACTION_FRAMEWORK);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const Char = {
|
|
1773
|
+
/**
|
|
1774
|
+
* The separator between the namespace and the action name in the
|
|
1775
|
+
* jsaction attribute value.
|
|
1776
|
+
*/
|
|
1777
|
+
NAMESPACE_ACTION_SEPARATOR: '.',
|
|
1778
|
+
/**
|
|
1779
|
+
* The separator between the event name and action in the jsaction
|
|
1780
|
+
* attribute value.
|
|
1781
|
+
*/
|
|
1782
|
+
EVENT_ACTION_SEPARATOR: ':',
|
|
1783
|
+
/**
|
|
1784
|
+
* The separator between the logged oi attribute values in the &oi=
|
|
1785
|
+
* URL parameter value.
|
|
1786
|
+
*/
|
|
1787
|
+
OI_SEPARATOR: '.',
|
|
1788
|
+
/**
|
|
1789
|
+
* The separator between the key and the value pairs in the &cad=
|
|
1790
|
+
* URL parameter value.
|
|
1791
|
+
*/
|
|
1792
|
+
CAD_KEY_VALUE_SEPARATOR: ':',
|
|
1793
|
+
/**
|
|
1794
|
+
* The separator between the key-value pairs in the &cad= URL
|
|
1795
|
+
* parameter value.
|
|
1796
|
+
*/
|
|
1797
|
+
CAD_SEPARATOR: ',',
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* Receives a DOM event, determines the jsaction associated with the source
|
|
1802
|
+
* element of the DOM event, and invokes the handler associated with the
|
|
1803
|
+
* jsaction.
|
|
1804
|
+
*/
|
|
1805
|
+
class Dispatcher {
|
|
1806
|
+
/**
|
|
1807
|
+
* Receives a DOM event, determines the jsaction associated with the source
|
|
1808
|
+
* element of the DOM event, and invokes the handler associated with the
|
|
1809
|
+
* jsaction.
|
|
1810
|
+
*
|
|
1811
|
+
* @param getHandler A function that knows how to get the handler for a
|
|
1812
|
+
* given event info.
|
|
1813
|
+
*/
|
|
1814
|
+
constructor(getHandler, { stopPropagation = false, eventReplayer = undefined, } = {}) {
|
|
1815
|
+
this.getHandler = getHandler;
|
|
1816
|
+
/**
|
|
1817
|
+
* The actions that are registered for this Dispatcher instance.
|
|
1818
|
+
* This should be the primary one used once migration off of registerHandlers
|
|
1819
|
+
* is done.
|
|
1820
|
+
*/
|
|
1821
|
+
this.actions = {};
|
|
1822
|
+
/** A map of global event handlers, where each key is an event type. */
|
|
1823
|
+
this.globalHandlers = new Map();
|
|
1824
|
+
this.baseDispatcher = new BaseDispatcher((eventInfoWrapper, isGlobalDispatch) => {
|
|
1825
|
+
this.dispatchToHandler(eventInfoWrapper, isGlobalDispatch);
|
|
1826
|
+
}, {
|
|
1827
|
+
eventReplayer: (eventInfoWrappers) => {
|
|
1828
|
+
this.eventReplayer?.(eventInfoWrappers, this);
|
|
1829
|
+
},
|
|
1830
|
+
});
|
|
1831
|
+
this.stopPropagation = stopPropagation;
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Receives an event or the event queue from the EventContract. The event
|
|
1835
|
+
* queue is copied and it attempts to replay.
|
|
1836
|
+
* If event info is passed in it looks for an action handler that can handle
|
|
1837
|
+
* the given event. If there is no handler registered queues the event and
|
|
1838
|
+
* checks if a loader is registered for the given namespace. If so, calls it.
|
|
1839
|
+
*
|
|
1840
|
+
* Alternatively, if in global dispatch mode, calls all registered global
|
|
1841
|
+
* handlers for the appropriate event type.
|
|
1842
|
+
*
|
|
1843
|
+
* The three functionalities of this call are deliberately not split into
|
|
1844
|
+
* three methods (and then declared as an abstract interface), because the
|
|
1845
|
+
* interface is used by EventContract, which lives in a different jsbinary.
|
|
1846
|
+
* Therefore the interface between the three is defined entirely in terms that
|
|
1847
|
+
* are invariant under jscompiler processing (Function and Array, as opposed
|
|
1848
|
+
* to a custom type with method names).
|
|
1849
|
+
*
|
|
1850
|
+
* @param eventInfo The info for the event that triggered this call or the
|
|
1851
|
+
* queue of events from EventContract.
|
|
1852
|
+
* @param isGlobalDispatch If true, dispatches a global event instead of a
|
|
1853
|
+
* regular jsaction handler.
|
|
1854
|
+
* @return An `EventInfo` for the `EventContract` to handle again if the
|
|
1855
|
+
* `Dispatcher` tried to resolve an a11y event as a click but failed.
|
|
1856
|
+
*/
|
|
1857
|
+
dispatch(eventInfo, isGlobalDispatch) {
|
|
1858
|
+
return this.baseDispatcher.dispatch(eventInfo, isGlobalDispatch);
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Dispatches an `EventInfoWrapper`.
|
|
1862
|
+
*/
|
|
1863
|
+
dispatchToHandler(eventInfoWrapper, isGlobalDispatch) {
|
|
1864
|
+
if (isGlobalDispatch) {
|
|
1865
|
+
// Skip everything related to jsaction handlers, and execute the global
|
|
1866
|
+
// handlers.
|
|
1867
|
+
const ev = eventInfoWrapper.getEvent();
|
|
1868
|
+
const eventTypeHandlers = this.globalHandlers.get(eventInfoWrapper.getEventType());
|
|
1869
|
+
let shouldPreventDefault = false;
|
|
1870
|
+
if (eventTypeHandlers) {
|
|
1871
|
+
for (const handler of eventTypeHandlers) {
|
|
1872
|
+
if (handler(ev) === false) {
|
|
1873
|
+
shouldPreventDefault = true;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (shouldPreventDefault) {
|
|
1878
|
+
preventDefault(ev);
|
|
1879
|
+
}
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
if (this.stopPropagation) {
|
|
1883
|
+
stopPropagation(eventInfoWrapper);
|
|
1884
|
+
}
|
|
1885
|
+
const action = eventInfoWrapper.getAction();
|
|
1886
|
+
let handler = undefined;
|
|
1887
|
+
if (this.getHandler) {
|
|
1888
|
+
handler = this.getHandler(eventInfoWrapper);
|
|
1889
|
+
}
|
|
1890
|
+
if (!handler) {
|
|
1891
|
+
handler = this.actions[action.name];
|
|
1892
|
+
}
|
|
1893
|
+
if (handler) {
|
|
1894
|
+
handler(eventInfoWrapper);
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
// No handler was found.
|
|
1898
|
+
this.baseDispatcher.queueEventInfoWrapper(eventInfoWrapper);
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Registers multiple methods all bound to the same object
|
|
1902
|
+
* instance. This is a common case: an application module binds
|
|
1903
|
+
* multiple of its methods under public names to the event contract of
|
|
1904
|
+
* the application. So we provide a shortcut for it.
|
|
1905
|
+
* Attempts to replay the queued events after registering the handlers.
|
|
1906
|
+
*
|
|
1907
|
+
* @param namespace The namespace of the jsaction name.
|
|
1908
|
+
*
|
|
1909
|
+
* @param instance The object to bind the methods to. If this is null, then
|
|
1910
|
+
* the functions are not bound, but directly added under the public names.
|
|
1911
|
+
*
|
|
1912
|
+
* @param methods A map from public name to functions that will be bound to
|
|
1913
|
+
* instance and registered as action under the public name. I.e. the
|
|
1914
|
+
* property names are the public names. The property values are the
|
|
1915
|
+
* methods of instance.
|
|
1916
|
+
*/
|
|
1917
|
+
registerEventInfoHandlers(namespace, instance, methods) {
|
|
1918
|
+
for (const [name, method] of Object.entries(methods)) {
|
|
1919
|
+
const handler = instance ? method.bind(instance) : method;
|
|
1920
|
+
if (namespace) {
|
|
1921
|
+
// Include a '.' separator between namespace name and action name.
|
|
1922
|
+
// In the case that no namespace name is provided, the jsaction name
|
|
1923
|
+
// consists of the action name only (no period).
|
|
1924
|
+
const fullName = namespace + Char.NAMESPACE_ACTION_SEPARATOR + name;
|
|
1925
|
+
this.actions[fullName] = handler;
|
|
1926
|
+
}
|
|
1927
|
+
else {
|
|
1928
|
+
this.actions[name] = handler;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
this.baseDispatcher.scheduleEventReplay();
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Unregisters an action. Provided as an easy way to reverse the effects of
|
|
1935
|
+
* registerHandlers.
|
|
1936
|
+
* @param namespace The namespace of the jsaction name.
|
|
1937
|
+
* @param name The action name to unbind.
|
|
1938
|
+
*/
|
|
1939
|
+
unregisterHandler(namespace, name) {
|
|
1940
|
+
const fullName = namespace ? namespace + Char.NAMESPACE_ACTION_SEPARATOR + name : name;
|
|
1941
|
+
delete this.actions[fullName];
|
|
1942
|
+
}
|
|
1943
|
+
/** Registers a global event handler. */
|
|
1944
|
+
registerGlobalHandler(eventType, handler) {
|
|
1945
|
+
if (!this.globalHandlers.has(eventType)) {
|
|
1946
|
+
this.globalHandlers.set(eventType, new Set([handler]));
|
|
1947
|
+
}
|
|
1948
|
+
else {
|
|
1949
|
+
this.globalHandlers.get(eventType).add(handler);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
/** Unregisters a global event handler. */
|
|
1953
|
+
unregisterGlobalHandler(eventType, handler) {
|
|
1954
|
+
if (this.globalHandlers.has(eventType)) {
|
|
1955
|
+
this.globalHandlers.get(eventType).delete(handler);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Checks whether there is an action registered under the given
|
|
1960
|
+
* name. This returns true if there is a namespace handler, even
|
|
1961
|
+
* if it can not yet handle the event.
|
|
1962
|
+
*
|
|
1963
|
+
* @param name Action name.
|
|
1964
|
+
* @return Whether the name is registered.
|
|
1965
|
+
* @see #canDispatch
|
|
1966
|
+
*/
|
|
1967
|
+
hasAction(name) {
|
|
1968
|
+
return this.actions.hasOwnProperty(name);
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Whether this dispatcher can dispatch the event. This can be used by
|
|
1972
|
+
* event replayer to check whether the dispatcher can replay an event.
|
|
1973
|
+
*/
|
|
1974
|
+
canDispatch(eventInfoWrapper) {
|
|
1975
|
+
const action = eventInfoWrapper.getAction();
|
|
1976
|
+
if (!action) {
|
|
1977
|
+
return false;
|
|
1978
|
+
}
|
|
1979
|
+
return this.hasAction(action.name);
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Sets the event replayer, enabling queued events to be replayed when actions
|
|
1983
|
+
* are bound. To replay events, you must register the dispatcher to the
|
|
1984
|
+
* contract after setting the `EventReplayer`. The event replayer takes as
|
|
1985
|
+
* parameters the queue of events and the dispatcher (used to check whether
|
|
1986
|
+
* actions have handlers registered and can be replayed). The event replayer
|
|
1987
|
+
* is also responsible for dequeuing events.
|
|
1988
|
+
*
|
|
1989
|
+
* Example: An event replayer that replays only the last event.
|
|
1990
|
+
*
|
|
1991
|
+
* const dispatcher = new Dispatcher();
|
|
1992
|
+
* // ...
|
|
1993
|
+
* dispatcher.setEventReplayer((queue, dispatcher) => {
|
|
1994
|
+
* const lastEventInfoWrapper = queue[queue.length -1];
|
|
1995
|
+
* if (dispatcher.canDispatch(lastEventInfoWrapper.getAction())) {
|
|
1996
|
+
* jsaction.replay.replayEvent(
|
|
1997
|
+
* lastEventInfoWrapper.getEvent(),
|
|
1998
|
+
* lastEventInfoWrapper.getTargetElement(),
|
|
1999
|
+
* lastEventInfoWrapper.getEventType(),
|
|
2000
|
+
* );
|
|
2001
|
+
* queue.length = 0;
|
|
2002
|
+
* }
|
|
2003
|
+
* });
|
|
2004
|
+
*
|
|
2005
|
+
* @param eventReplayer It allows elements to be replayed and dequeuing.
|
|
2006
|
+
*/
|
|
2007
|
+
setEventReplayer(eventReplayer) {
|
|
2008
|
+
this.eventReplayer = eventReplayer;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
/** Stop propagation for an `EventInfo`. */
|
|
2012
|
+
function stopPropagation(eventInfoWrapper) {
|
|
2013
|
+
if (isGecko &&
|
|
2014
|
+
(eventInfoWrapper.getTargetElement().tagName === 'INPUT' ||
|
|
2015
|
+
eventInfoWrapper.getTargetElement().tagName === 'TEXTAREA') &&
|
|
2016
|
+
eventInfoWrapper.getEventType() === EventType.FOCUS) {
|
|
2017
|
+
/**
|
|
2018
|
+
* Do nothing since stopping propagation on a focus event on an input
|
|
2019
|
+
* element in Firefox makes the text cursor disappear:
|
|
2020
|
+
* https://bugzilla.mozilla.org/show_bug.cgi?id=509684
|
|
2021
|
+
*/
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
const event = eventInfoWrapper.getEvent();
|
|
2025
|
+
// There are some cases where users of the `Dispatcher` will call dispatch
|
|
2026
|
+
// with a fake event that does not support `stopPropagation`.
|
|
2027
|
+
if (!event.stopPropagation) {
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
event.stopPropagation();
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Registers deferred functionality for an EventContract and a Jsaction
|
|
2034
|
+
* Dispatcher.
|
|
2035
|
+
*/
|
|
2036
|
+
function registerDispatcher(eventContract, dispatcher) {
|
|
2037
|
+
eventContract.ecrd((eventInfo, globalDispatch) => {
|
|
2038
|
+
return dispatcher.dispatch(eventInfo, globalDispatch);
|
|
2039
|
+
}, Restriction.I_AM_THE_JSACTION_FRAMEWORK);
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
/**
|
|
2043
|
+
* Whether the user agent is running on iOS.
|
|
2044
|
+
*/
|
|
2045
|
+
const isIos = typeof navigator !== 'undefined' && /iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
2046
|
+
/**
|
|
2047
|
+
* A class representing a container node and all the event handlers
|
|
2048
|
+
* installed on it. Used so that handlers can be cleaned up if the
|
|
2049
|
+
* container is removed from the contract.
|
|
2050
|
+
*/
|
|
2051
|
+
class EventContractContainer {
|
|
2052
|
+
/**
|
|
2053
|
+
* @param element The container Element.
|
|
2054
|
+
*/
|
|
2055
|
+
constructor(element) {
|
|
2056
|
+
this.element = element;
|
|
2057
|
+
/**
|
|
2058
|
+
* Array of event handlers and their corresponding event types that are
|
|
2059
|
+
* installed on this container.
|
|
2060
|
+
*
|
|
2061
|
+
*/
|
|
2062
|
+
this.handlerInfos = [];
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Installs the provided installer on the element owned by this container,
|
|
2066
|
+
* and maintains a reference to resulting handler in order to remove it
|
|
2067
|
+
* later if desired.
|
|
2068
|
+
*/
|
|
2069
|
+
addEventListener(eventType, getHandler) {
|
|
2070
|
+
// In iOS, event bubbling doesn't happen automatically in any DOM element,
|
|
2071
|
+
// unless it has an onclick attribute or DOM event handler attached to it.
|
|
2072
|
+
// This breaks JsAction in some cases. See "Making Elements Clickable"
|
|
2073
|
+
// section at http://goo.gl/2VoGnB.
|
|
2074
|
+
//
|
|
2075
|
+
// A workaround for this issue is to change the CSS cursor style to 'pointer'
|
|
2076
|
+
// for the container element, which magically turns on event bubbling. This
|
|
2077
|
+
// solution is described in the comments section at http://goo.gl/6pEO1z.
|
|
2078
|
+
//
|
|
2079
|
+
// We use a navigator.userAgent check here as this problem is present both
|
|
2080
|
+
// on Mobile Safari and thin WebKit wrappers, such as Chrome for iOS.
|
|
2081
|
+
if (isIos) {
|
|
2082
|
+
this.element.style.cursor = 'pointer';
|
|
2083
|
+
}
|
|
2084
|
+
this.handlerInfos.push(addEventListener(this.element, eventType, getHandler(this.element)));
|
|
2085
|
+
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Removes all the handlers installed on this container.
|
|
2088
|
+
*/
|
|
2089
|
+
cleanUp() {
|
|
2090
|
+
for (let i = 0; i < this.handlerInfos.length; i++) {
|
|
2091
|
+
removeEventListener(this.element, this.handlerInfos[i]);
|
|
2092
|
+
}
|
|
2093
|
+
this.handlerInfos = [];
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
/**
|
|
2098
|
+
* Update `EventInfo` to be `eventType = 'click'` and sets `a11yClickKey` if it
|
|
2099
|
+
* is a a11y click.
|
|
2100
|
+
*/
|
|
2101
|
+
function updateEventInfoForA11yClick(eventInfo) {
|
|
2102
|
+
if (!isActionKeyEvent(getEvent(eventInfo))) {
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
setA11yClickKey(eventInfo, true);
|
|
2106
|
+
// A 'click' triggered by a DOM keypress should be mapped to the 'click'
|
|
2107
|
+
// jsaction.
|
|
2108
|
+
setEventType(eventInfo, EventType.CLICK);
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Call `preventDefault` on an a11y click if it is space key or to prevent the
|
|
2112
|
+
* browser's default action for native HTML controls.
|
|
2113
|
+
*/
|
|
2114
|
+
function preventDefaultForA11yClick(eventInfo) {
|
|
2115
|
+
if (!getA11yClickKey(eventInfo) ||
|
|
2116
|
+
(!isSpaceKeyEvent(getEvent(eventInfo)) &&
|
|
2117
|
+
!shouldCallPreventDefaultOnNativeHtmlControl(getEvent(eventInfo)))) {
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
preventDefault(getEvent(eventInfo));
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Sets the `action` to `clickonly` for a click event that is not an a11y click
|
|
2124
|
+
* and if there is not already a click action.
|
|
2125
|
+
*/
|
|
2126
|
+
function populateClickOnlyAction(actionElement, eventInfo, actionMap) {
|
|
2127
|
+
if (
|
|
2128
|
+
// If there's already an action, don't attempt to set a CLICKONLY
|
|
2129
|
+
getAction(eventInfo) ||
|
|
2130
|
+
// Only attempt CLICKONLY if the type is CLICK
|
|
2131
|
+
getEventType(eventInfo) !== EventType.CLICK ||
|
|
2132
|
+
// a11y clicks are never CLICKONLY
|
|
2133
|
+
getA11yClickKey(eventInfo) ||
|
|
2134
|
+
actionMap[EventType.CLICKONLY] === undefined) {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
setEventType(eventInfo, EventType.CLICKONLY);
|
|
2138
|
+
setAction(eventInfo, actionMap[EventType.CLICKONLY], actionElement);
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
var Attribute;
|
|
2142
|
+
(function (Attribute) {
|
|
2143
|
+
/**
|
|
2144
|
+
* The jsaction attribute defines a mapping of a DOM event to a
|
|
2145
|
+
* generic event (aka jsaction), to which the actual event handlers
|
|
2146
|
+
* that implement the behavior of the application are bound. The
|
|
2147
|
+
* value is a semicolon separated list of colon separated pairs of
|
|
2148
|
+
* an optional DOM event name and a jsaction name. If the optional
|
|
2149
|
+
* DOM event name is omitted, 'click' is assumed. The jsaction names
|
|
2150
|
+
* are dot separated pairs of a namespace and a simple jsaction
|
|
2151
|
+
* name. If the namespace is absent, it is taken from the closest
|
|
2152
|
+
* ancestor element with a jsnamespace attribute, if there is
|
|
2153
|
+
* any. If there is no ancestor with a jsnamespace attribute, the
|
|
2154
|
+
* simple name is assumed to be the jsaction name.
|
|
2155
|
+
*
|
|
2156
|
+
* Used by EventContract.
|
|
2157
|
+
*/
|
|
2158
|
+
Attribute["JSACTION"] = "jsaction";
|
|
2159
|
+
/**
|
|
2160
|
+
* The jsnamespace attribute provides the namespace part of the
|
|
2161
|
+
* jaction names occurring in the jsaction attribute where it's
|
|
2162
|
+
* missing.
|
|
2163
|
+
*
|
|
2164
|
+
* Used by EventContract.
|
|
2165
|
+
*/
|
|
2166
|
+
Attribute["JSNAMESPACE"] = "jsnamespace";
|
|
2167
|
+
/**
|
|
2168
|
+
* The oi attribute is a log impression tag for impression logging
|
|
2169
|
+
* and action tracking. For an element that carries a jsaction
|
|
2170
|
+
* attribute, the element is identified for the purpose of
|
|
2171
|
+
* impression logging and click tracking by the dot separated path
|
|
2172
|
+
* of all oi attributes in the chain of ancestors of the element.
|
|
2173
|
+
*
|
|
2174
|
+
* Used by ActionFlow.
|
|
2175
|
+
*/
|
|
2176
|
+
Attribute["OI"] = "oi";
|
|
2177
|
+
/**
|
|
2178
|
+
* The ved attribute is an encoded ClickTrackingCGI proto to track
|
|
2179
|
+
* visual elements.
|
|
2180
|
+
*
|
|
2181
|
+
* Used by ActionFlow.
|
|
2182
|
+
*/
|
|
2183
|
+
Attribute["VED"] = "ved";
|
|
2184
|
+
/**
|
|
2185
|
+
* The vet attribute is the visual element type used to identify tracked
|
|
2186
|
+
* visual elements.
|
|
2187
|
+
*/
|
|
2188
|
+
Attribute["VET"] = "vet";
|
|
2189
|
+
/**
|
|
2190
|
+
* Support for iteration on reprocessing.
|
|
2191
|
+
*
|
|
2192
|
+
* Used by ActionFlow.
|
|
2193
|
+
*/
|
|
2194
|
+
Attribute["JSINSTANCE"] = "jsinstance";
|
|
2195
|
+
/**
|
|
2196
|
+
* All click jsactions that happen on the element that carries this
|
|
2197
|
+
* attribute or its descendants are automatically logged.
|
|
2198
|
+
* Impressions of jsactions on these elements are tracked too, if
|
|
2199
|
+
* requested by the impression() method of ActionFlow.
|
|
2200
|
+
*
|
|
2201
|
+
* Used by ActionFlow.
|
|
2202
|
+
*/
|
|
2203
|
+
Attribute["JSTRACK"] = "jstrack";
|
|
2204
|
+
})(Attribute || (Attribute = {}));
|
|
2205
|
+
|
|
2206
|
+
/** All properties that are used by jsaction. */
|
|
2207
|
+
var Property;
|
|
2208
|
+
(function (Property) {
|
|
2209
|
+
/**
|
|
2210
|
+
* The parsed value of the jsaction attribute is stored in this
|
|
2211
|
+
* property on the DOM node. The parsed value is an Object. The
|
|
2212
|
+
* property names of the object are the events; the values are the
|
|
2213
|
+
* names of the actions. This property is attached even on nodes
|
|
2214
|
+
* that don't have a jsaction attribute as an optimization, because
|
|
2215
|
+
* property lookup is faster than attribute access.
|
|
2216
|
+
*/
|
|
2217
|
+
Property["JSACTION"] = "__jsaction";
|
|
2218
|
+
/**
|
|
2219
|
+
* The parsed value of the jsnamespace attribute is stored in this
|
|
2220
|
+
* property on the DOM node.
|
|
2221
|
+
*/
|
|
2222
|
+
Property["JSNAMESPACE"] = "__jsnamespace";
|
|
2223
|
+
/** The value of the oi attribute as a property, for faster access. */
|
|
2224
|
+
Property["OI"] = "__oi";
|
|
2225
|
+
/**
|
|
2226
|
+
* The owner property references an a logical owner for a DOM node. JSAction
|
|
2227
|
+
* will follow this reference instead of parentNode when traversing the DOM
|
|
2228
|
+
* to find jsaction attributes. This allows overlaying a logical structure
|
|
2229
|
+
* over a document where the DOM structure can't reflect that structure.
|
|
2230
|
+
*/
|
|
2231
|
+
Property["OWNER"] = "__owner";
|
|
2232
|
+
})(Property || (Property = {}));
|
|
2233
|
+
|
|
2234
|
+
/**
|
|
2235
|
+
* Map from jsaction annotation to a parsed map from event name to action name.
|
|
2236
|
+
*/
|
|
2237
|
+
const parseCache = {};
|
|
2238
|
+
/**
|
|
2239
|
+
* Reads the jsaction parser cache from the given DOM Element.
|
|
2240
|
+
*
|
|
2241
|
+
* @param element .
|
|
2242
|
+
* @return Map from event to qualified name of the jsaction bound to it.
|
|
2243
|
+
*/
|
|
2244
|
+
function get(element) {
|
|
2245
|
+
// @ts-ignore
|
|
2246
|
+
return element[Property.JSACTION];
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Writes the jsaction parser cache to the given DOM Element.
|
|
2250
|
+
*
|
|
2251
|
+
* @param element .
|
|
2252
|
+
* @param actionMap Map from event to qualified name of the jsaction bound to
|
|
2253
|
+
* it.
|
|
2254
|
+
*/
|
|
2255
|
+
function set(element, actionMap) {
|
|
2256
|
+
// @ts-ignore
|
|
2257
|
+
element[Property.JSACTION] = actionMap;
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Looks up the parsed action map from the source jsaction attribute value.
|
|
2261
|
+
*
|
|
2262
|
+
* @param text Unparsed jsaction attribute value.
|
|
2263
|
+
* @return Parsed jsaction attribute value, if already present in the cache.
|
|
2264
|
+
*/
|
|
2265
|
+
function getParsed(text) {
|
|
2266
|
+
return parseCache[text];
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Inserts the parse result for the given source jsaction value into the cache.
|
|
2270
|
+
*
|
|
2271
|
+
* @param text Unparsed jsaction attribute value.
|
|
2272
|
+
* @param parsed Attribute value parsed into the action map.
|
|
2273
|
+
*/
|
|
2274
|
+
function setParsed(text, parsed) {
|
|
2275
|
+
parseCache[text] = parsed;
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Clears the jsaction parser cache from the given DOM Element.
|
|
2279
|
+
*
|
|
2280
|
+
* @param element .
|
|
2281
|
+
*/
|
|
2282
|
+
function clear(element) {
|
|
2283
|
+
if (Property.JSACTION in element) {
|
|
2284
|
+
delete element[Property.JSACTION];
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Reads the cached jsaction namespace from the given DOM
|
|
2289
|
+
* Element. Undefined means there is no cached value; null is a cached
|
|
2290
|
+
* jsnamespace attribute that's absent.
|
|
2291
|
+
*
|
|
2292
|
+
* @param element .
|
|
2293
|
+
* @return .
|
|
2294
|
+
*/
|
|
2295
|
+
function getNamespace(element) {
|
|
2296
|
+
// @ts-ignore
|
|
2297
|
+
return element[Property.JSNAMESPACE];
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Writes the cached jsaction namespace to the given DOM Element. Null
|
|
2301
|
+
* represents a jsnamespace attribute that's absent.
|
|
2302
|
+
*
|
|
2303
|
+
* @param element .
|
|
2304
|
+
* @param jsnamespace .
|
|
2305
|
+
*/
|
|
2306
|
+
function setNamespace(element, jsnamespace) {
|
|
2307
|
+
// @ts-ignore
|
|
2308
|
+
element[Property.JSNAMESPACE] = jsnamespace;
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Clears the cached jsaction namespace from the given DOM Element.
|
|
2312
|
+
*
|
|
2313
|
+
* @param element .
|
|
2314
|
+
*/
|
|
2315
|
+
function clearNamespace(element) {
|
|
2316
|
+
if (Property.JSNAMESPACE in element) {
|
|
2317
|
+
delete element[Property.JSNAMESPACE];
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
/**
|
|
2322
|
+
* @define Support for jsnamespace attribute. This flag can be overridden in a
|
|
2323
|
+
* build rule to trim down the EventContract's binary size.
|
|
2324
|
+
*/
|
|
2325
|
+
const JSNAMESPACE_SUPPORT = true;
|
|
2326
|
+
/**
|
|
2327
|
+
* @define Handles a11y click casting in the dispatcher rather than
|
|
2328
|
+
* the event contract. When enabled, it will enable
|
|
2329
|
+
* EventContract.A11Y_CLICK_SUPPORT as well as both are required for this
|
|
2330
|
+
* functionality.
|
|
2331
|
+
*/
|
|
2332
|
+
const A11Y_SUPPORT_IN_DISPATCHER = false;
|
|
2333
|
+
/**
|
|
2334
|
+
* @define Support for accessible click actions. This flag can be overridden in
|
|
2335
|
+
* a build rule.
|
|
2336
|
+
*/
|
|
2337
|
+
const A11Y_CLICK_SUPPORT_FLAG_ENABLED = false;
|
|
2338
|
+
/**
|
|
2339
|
+
* Enables a11y click casting when either A11Y_CLICK_SUPPORT_FLAG_ENABLED or
|
|
2340
|
+
* A11Y_SUPPORT_IN_DISPATCHER.
|
|
2341
|
+
*/
|
|
2342
|
+
const A11Y_CLICK_SUPPORT = A11Y_CLICK_SUPPORT_FLAG_ENABLED || A11Y_SUPPORT_IN_DISPATCHER;
|
|
2343
|
+
/**
|
|
2344
|
+
* @define Support for the non-bubbling mouseenter and mouseleave events. This
|
|
2345
|
+
* flag can be overridden in a build rule.
|
|
2346
|
+
*/
|
|
2347
|
+
const MOUSE_SPECIAL_SUPPORT = false;
|
|
2348
|
+
/**
|
|
2349
|
+
* @define Call stopPropagation on handled events. When integrating with
|
|
2350
|
+
* non-jsaction event handler based code, you will likely want to turn this flag
|
|
2351
|
+
* off. While most event handlers will continue to work, jsaction binds focus
|
|
2352
|
+
* and blur events in the capture phase and thus with stopPropagation, none of
|
|
2353
|
+
* your non-jsaction-handlers will ever see it.
|
|
2354
|
+
*/
|
|
2355
|
+
const STOP_PROPAGATION = true;
|
|
2356
|
+
/**
|
|
2357
|
+
* @define Support for custom events, which are type EventType.CUSTOM. These are
|
|
2358
|
+
* native DOM events with an additional type field and an optional payload.
|
|
2359
|
+
*/
|
|
2360
|
+
const CUSTOM_EVENT_SUPPORT = false;
|
|
2361
|
+
|
|
2362
|
+
/**
|
|
2363
|
+
* @fileoverview Implements the local event handling contract. This
|
|
2364
|
+
* allows DOM objects in a container that enters into this contract to
|
|
2365
|
+
* define event handlers which are executed in a local context.
|
|
2366
|
+
*
|
|
2367
|
+
* One EventContract instance can manage the contract for multiple
|
|
2368
|
+
* containers, which are added using the addContainer() method.
|
|
2369
|
+
*
|
|
2370
|
+
* Events can be registered using the addEvent() method.
|
|
2371
|
+
*
|
|
2372
|
+
* A Dispatcher is added using the registerDispatcher() method. Until there is
|
|
2373
|
+
* a dispatcher, events are queued. The idea is that the EventContract
|
|
2374
|
+
* class is inlined in the HTML of the top level page and instantiated
|
|
2375
|
+
* right after the start of <body>. The Dispatcher class is contained
|
|
2376
|
+
* in the external deferred js, and instantiated and registered with
|
|
2377
|
+
* EventContract when the external javascript in the page loads. The
|
|
2378
|
+
* external javascript will also register the jsaction handlers, which
|
|
2379
|
+
* then pick up the queued events at the time of registration.
|
|
2380
|
+
*
|
|
2381
|
+
* Since this class is meant to be inlined in the main page HTML, the
|
|
2382
|
+
* size of the binary compiled from this file MUST be kept as small as
|
|
2383
|
+
* possible and thus its dependencies to a minimum.
|
|
2384
|
+
*/
|
|
2385
|
+
const DEFAULT_EVENT_TYPE = EventType.CLICK;
|
|
2386
|
+
/**
|
|
2387
|
+
* Since maps from event to action are immutable we can use a single map
|
|
2388
|
+
* to represent the empty map.
|
|
2389
|
+
*/
|
|
2390
|
+
const EMPTY_ACTION_MAP = {};
|
|
2391
|
+
/**
|
|
2392
|
+
* This regular expression matches a semicolon.
|
|
2393
|
+
*/
|
|
2394
|
+
const REGEXP_SEMICOLON = /\s*;\s*/;
|
|
2395
|
+
/**
|
|
2396
|
+
* EventContract intercepts events in the bubbling phase at the
|
|
2397
|
+
* boundary of a container element, and maps them to generic actions
|
|
2398
|
+
* which are specified using the custom jsaction attribute in
|
|
2399
|
+
* HTML. Behavior of the application is then specified in terms of
|
|
2400
|
+
* handler for such actions, cf. jsaction.Dispatcher in dispatcher.js.
|
|
2401
|
+
*
|
|
2402
|
+
* This has several benefits: (1) No DOM event handlers need to be
|
|
2403
|
+
* registered on the specific elements in the UI. (2) The set of
|
|
2404
|
+
* events that the application has to handle can be specified in terms
|
|
2405
|
+
* of the semantics of the application, rather than in terms of DOM
|
|
2406
|
+
* events. (3) Invocation of handlers can be delayed and handlers can
|
|
2407
|
+
* be delay loaded in a generic way.
|
|
2408
|
+
*/
|
|
2409
|
+
class EventContract {
|
|
2410
|
+
static { this.CUSTOM_EVENT_SUPPORT = CUSTOM_EVENT_SUPPORT; }
|
|
2411
|
+
static { this.STOP_PROPAGATION = STOP_PROPAGATION; }
|
|
2412
|
+
static { this.A11Y_SUPPORT_IN_DISPATCHER = A11Y_SUPPORT_IN_DISPATCHER; }
|
|
2413
|
+
static { this.A11Y_CLICK_SUPPORT = A11Y_CLICK_SUPPORT; }
|
|
2414
|
+
static { this.MOUSE_SPECIAL_SUPPORT = MOUSE_SPECIAL_SUPPORT; }
|
|
2415
|
+
static { this.JSNAMESPACE_SUPPORT = JSNAMESPACE_SUPPORT; }
|
|
2416
|
+
constructor(containerManager) {
|
|
2417
|
+
/**
|
|
2418
|
+
* The DOM events which this contract covers. Used to prevent double
|
|
2419
|
+
* registration of event types. The value of the map is the
|
|
2420
|
+
* internally created DOM event handler function that handles the
|
|
2421
|
+
* DOM events. See addEvent().
|
|
2422
|
+
*
|
|
2423
|
+
*/
|
|
2424
|
+
this.eventHandlers = {};
|
|
2425
|
+
this.browserEventTypeToExtraEventTypes = {};
|
|
2426
|
+
/**
|
|
2427
|
+
* The dispatcher function. Events are passed to this function for
|
|
2428
|
+
* handling once it was set using the registerDispatcher() method. This is
|
|
2429
|
+
* done because the function is passed from another jsbinary, so passing the
|
|
2430
|
+
* instance and invoking the method here would require to leave the method
|
|
2431
|
+
* unobfuscated.
|
|
2432
|
+
*/
|
|
2433
|
+
this.dispatcher = null;
|
|
2434
|
+
/**
|
|
2435
|
+
* The list of suspended `EventInfo` that will be dispatched
|
|
2436
|
+
* as soon as the `Dispatcher` is registered.
|
|
2437
|
+
*/
|
|
2438
|
+
this.queuedEventInfos = [];
|
|
2439
|
+
/** Whether a11y click support has been loaded or not. */
|
|
2440
|
+
this.hasA11yClickSupport = false;
|
|
2441
|
+
/** Whether to add an a11y click listener. */
|
|
2442
|
+
this.addA11yClickListener = EventContract.A11Y_SUPPORT_IN_DISPATCHER;
|
|
2443
|
+
this.updateEventInfoForA11yClick = undefined;
|
|
2444
|
+
this.preventDefaultForA11yClick = undefined;
|
|
2445
|
+
this.populateClickOnlyAction = undefined;
|
|
2446
|
+
this.containerManager = containerManager;
|
|
2447
|
+
if (EventContract.CUSTOM_EVENT_SUPPORT) {
|
|
2448
|
+
this.addEvent(EventType.CUSTOM);
|
|
2449
|
+
}
|
|
2450
|
+
if (EventContract.A11Y_CLICK_SUPPORT) {
|
|
2451
|
+
// Add a11y click support to the `EventContract`.
|
|
2452
|
+
this.addA11yClickSupport();
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
handleEvent(eventType, event, container) {
|
|
2456
|
+
const eventInfo = createEventInfoFromParameters(
|
|
2457
|
+
/* eventType= */ eventType,
|
|
2458
|
+
/* event= */ event,
|
|
2459
|
+
/* targetElement= */ event.target,
|
|
2460
|
+
/* container= */ container,
|
|
2461
|
+
/* timestamp= */ Date.now());
|
|
2462
|
+
this.handleEventInfo(eventInfo);
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Handle an `EventInfo`.
|
|
2466
|
+
* @param allowRehandling Used in the case of a11y click casting to prevent
|
|
2467
|
+
* us from trying to rehandle in an infinite loop.
|
|
2468
|
+
*/
|
|
2469
|
+
handleEventInfo(eventInfo, allowRehandling = true) {
|
|
2470
|
+
if (!this.dispatcher) {
|
|
2471
|
+
// All events are queued when the dispatcher isn't yet loaded.
|
|
2472
|
+
setIsReplay(eventInfo, true);
|
|
2473
|
+
this.queuedEventInfos?.push(eventInfo);
|
|
2474
|
+
}
|
|
2475
|
+
if (EventContract.CUSTOM_EVENT_SUPPORT &&
|
|
2476
|
+
getEventType(eventInfo) === EventType.CUSTOM) {
|
|
2477
|
+
const detail = getEvent(eventInfo).detail;
|
|
2478
|
+
// For custom events, use a secondary dispatch based on the internal
|
|
2479
|
+
// custom type of the event.
|
|
2480
|
+
if (!detail || !detail['_type']) {
|
|
2481
|
+
// This should never happen.
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
setEventType(eventInfo, detail['_type']);
|
|
2485
|
+
}
|
|
2486
|
+
this.populateAction(eventInfo);
|
|
2487
|
+
if (this.dispatcher &&
|
|
2488
|
+
!getEvent(eventInfo)[Attribute$1.SKIP_GLOBAL_DISPATCH]) {
|
|
2489
|
+
const globalEventInfo = cloneEventInfo(eventInfo);
|
|
2490
|
+
// In some cases, `populateAction` will rewrite `click` events to
|
|
2491
|
+
// `clickonly`. Revert back to a regular click, otherwise we won't be able
|
|
2492
|
+
// to execute global event handlers registered on click events.
|
|
2493
|
+
if (getEventType(globalEventInfo) === EventType.CLICKONLY) {
|
|
2494
|
+
setEventType(globalEventInfo, EventType.CLICK);
|
|
2495
|
+
}
|
|
2496
|
+
this.dispatcher(globalEventInfo, /* dispatch global event */ true);
|
|
2497
|
+
}
|
|
2498
|
+
const action = getAction(eventInfo);
|
|
2499
|
+
if (!action && !checkDispatcherForA11yClick(eventInfo)) {
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
if (this.dispatcher) {
|
|
2503
|
+
if (action &&
|
|
2504
|
+
shouldPreventDefaultBeforeDispatching(getActionElement(action), eventInfo)) {
|
|
2505
|
+
preventDefault(getEvent(eventInfo));
|
|
2506
|
+
}
|
|
2507
|
+
const unresolvedEventInfo = this.dispatcher(eventInfo);
|
|
2508
|
+
if (unresolvedEventInfo && allowRehandling) {
|
|
2509
|
+
// The dispatcher only returns an event for MAYBE_CLICK_EVENT_TYPE
|
|
2510
|
+
// events that can't be casted to a click. We run it through the
|
|
2511
|
+
// handler again to find keydown actions for it.
|
|
2512
|
+
this.handleEventInfo(unresolvedEventInfo, /* allowRehandling= */ false);
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Searches for a jsaction that the DOM event maps to and creates an
|
|
2519
|
+
* object containing event information used for dispatching by
|
|
2520
|
+
* jsaction.Dispatcher. This method populates the `action` and `actionElement`
|
|
2521
|
+
* fields of the EventInfo object passed in by finding the first
|
|
2522
|
+
* jsaction attribute above the target Node of the event, and below
|
|
2523
|
+
* the container Node, that specifies a jsaction for the event
|
|
2524
|
+
* type. If no such jsaction is found, then action is undefined.
|
|
2525
|
+
*
|
|
2526
|
+
* @param eventInfo `EventInfo` to set `action` and `actionElement` if an
|
|
2527
|
+
* action is found on any `Element` in the path of the `Event`.
|
|
2528
|
+
*/
|
|
2529
|
+
populateAction(eventInfo) {
|
|
2530
|
+
// We distinguish modified and plain clicks in order to support the
|
|
2531
|
+
// default browser behavior of modified clicks on links; usually to
|
|
2532
|
+
// open the URL of the link in new tab or new window on ctrl/cmd
|
|
2533
|
+
// click. A DOM 'click' event is mapped to the jsaction 'click'
|
|
2534
|
+
// event iff there is no modifier present on the event. If there is
|
|
2535
|
+
// a modifier, it's mapped to 'clickmod' instead.
|
|
2536
|
+
//
|
|
2537
|
+
// It's allowed to omit the event in the jsaction attribute. In that
|
|
2538
|
+
// case, 'click' is assumed. Thus the following two are equivalent:
|
|
2539
|
+
//
|
|
2540
|
+
// <a href="someurl" jsaction="gna.fu">
|
|
2541
|
+
// <a href="someurl" jsaction="click:gna.fu">
|
|
2542
|
+
//
|
|
2543
|
+
// For unmodified clicks, EventContract invokes the jsaction
|
|
2544
|
+
// 'gna.fu'. For modified clicks, EventContract won't find a
|
|
2545
|
+
// suitable action and leave the event to be handled by the
|
|
2546
|
+
// browser.
|
|
2547
|
+
//
|
|
2548
|
+
// In order to also invoke a jsaction handler for a modifier click,
|
|
2549
|
+
// 'clickmod' needs to be used:
|
|
2550
|
+
//
|
|
2551
|
+
// <a href="someurl" jsaction="clickmod:gna.fu">
|
|
2552
|
+
//
|
|
2553
|
+
// EventContract invokes the jsaction 'gna.fu' for modified
|
|
2554
|
+
// clicks. Unmodified clicks are left to the browser.
|
|
2555
|
+
//
|
|
2556
|
+
// In order to set up the event contract to handle both clickonly and
|
|
2557
|
+
// clickmod, only addEvent(EventType.CLICK) is necessary.
|
|
2558
|
+
//
|
|
2559
|
+
// In order to set up the event contract to handle click,
|
|
2560
|
+
// addEvent() is necessary for CLICK, KEYDOWN, and KEYPRESS event types. If
|
|
2561
|
+
// a11y click support is enabled, addEvent() will set up the appropriate key
|
|
2562
|
+
// event handler automatically.
|
|
2563
|
+
if (getEventType(eventInfo) === EventType.CLICK &&
|
|
2564
|
+
isModifiedClickEvent(getEvent(eventInfo))) {
|
|
2565
|
+
setEventType(eventInfo, EventType.CLICKMOD);
|
|
2566
|
+
}
|
|
2567
|
+
else if (this.hasA11yClickSupport) {
|
|
2568
|
+
this.updateEventInfoForA11yClick(eventInfo);
|
|
2569
|
+
}
|
|
2570
|
+
else if (EventContract.A11Y_SUPPORT_IN_DISPATCHER &&
|
|
2571
|
+
getEventType(eventInfo) === EventType.KEYDOWN &&
|
|
2572
|
+
!getEvent(eventInfo)[Attribute$1.SKIP_A11Y_CHECK]) {
|
|
2573
|
+
// We use a string literal as this value needs to be referenced in the
|
|
2574
|
+
// dispatcher's binary.
|
|
2575
|
+
setEventType(eventInfo, Attribute$1.MAYBE_CLICK_EVENT_TYPE);
|
|
2576
|
+
}
|
|
2577
|
+
// Walk to the parent node, unless the node has a different owner in
|
|
2578
|
+
// which case we walk to the owner. Attempt to walk to host of a
|
|
2579
|
+
// shadow root if needed.
|
|
2580
|
+
let actionElement = getTargetElement(eventInfo);
|
|
2581
|
+
while (actionElement && actionElement !== getContainer(eventInfo)) {
|
|
2582
|
+
this.populateActionOnElement(actionElement, eventInfo);
|
|
2583
|
+
if (getAction(eventInfo)) {
|
|
2584
|
+
// An event is handled by at most one jsaction. Thus we stop at the
|
|
2585
|
+
// first matching jsaction specified in a jsaction attribute up the
|
|
2586
|
+
// ancestor chain of the event target node.
|
|
2587
|
+
break;
|
|
2588
|
+
}
|
|
2589
|
+
if (actionElement[Property.OWNER]) {
|
|
2590
|
+
actionElement = actionElement[Property.OWNER];
|
|
2591
|
+
continue;
|
|
2592
|
+
}
|
|
2593
|
+
if (actionElement.parentNode?.nodeName !== '#document-fragment') {
|
|
2594
|
+
actionElement = actionElement.parentNode;
|
|
2595
|
+
}
|
|
2596
|
+
else {
|
|
2597
|
+
actionElement = actionElement.parentNode?.host ?? null;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
const action = getAction(eventInfo);
|
|
2601
|
+
if (!action) {
|
|
2602
|
+
// No action found.
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
if (this.hasA11yClickSupport) {
|
|
2606
|
+
this.preventDefaultForA11yClick(eventInfo);
|
|
2607
|
+
}
|
|
2608
|
+
// We attempt to handle the mouseenter/mouseleave events here by
|
|
2609
|
+
// detecting whether the mouseover/mouseout events correspond to
|
|
2610
|
+
// entering/leaving an element.
|
|
2611
|
+
if (EventContract.MOUSE_SPECIAL_SUPPORT &&
|
|
2612
|
+
(getEventType(eventInfo) === EventType.MOUSEENTER ||
|
|
2613
|
+
getEventType(eventInfo) === EventType.MOUSELEAVE ||
|
|
2614
|
+
getEventType(eventInfo) === EventType.POINTERENTER ||
|
|
2615
|
+
getEventType(eventInfo) === EventType.POINTERLEAVE)) {
|
|
2616
|
+
// We attempt to handle the mouseenter/mouseleave events here by
|
|
2617
|
+
// detecting whether the mouseover/mouseout events correspond to
|
|
2618
|
+
// entering/leaving an element.
|
|
2619
|
+
if (isMouseSpecialEvent(getEvent(eventInfo), getEventType(eventInfo), getActionElement(action))) {
|
|
2620
|
+
// If both mouseover/mouseout and mouseenter/mouseleave events are
|
|
2621
|
+
// enabled, two separate handlers for mouseover/mouseout are
|
|
2622
|
+
// registered. Both handlers will see the same event instance
|
|
2623
|
+
// so we create a copy to avoid interfering with the dispatching of
|
|
2624
|
+
// the mouseover/mouseout event.
|
|
2625
|
+
const copiedEvent = createMouseSpecialEvent(getEvent(eventInfo), getActionElement(action));
|
|
2626
|
+
setEvent(eventInfo, copiedEvent);
|
|
2627
|
+
// Since the mouseenter/mouseleave events do not bubble, the target
|
|
2628
|
+
// of the event is technically the `actionElement` (the node with the
|
|
2629
|
+
// `jsaction` attribute)
|
|
2630
|
+
setTargetElement(eventInfo, getActionElement(action));
|
|
2631
|
+
}
|
|
2632
|
+
else {
|
|
2633
|
+
unsetAction(eventInfo);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
/**
|
|
2638
|
+
* Accesses the jsaction map on a node and retrieves the name of the
|
|
2639
|
+
* action the given event is mapped to, if any. It parses the
|
|
2640
|
+
* attribute value and stores it in a property on the node for
|
|
2641
|
+
* subsequent retrieval without re-parsing and re-accessing the
|
|
2642
|
+
* attribute. In order to fully qualify jsaction names using a
|
|
2643
|
+
* namespace, the DOM is searched starting at the current node and
|
|
2644
|
+
* going through ancestor nodes until a jsnamespace attribute is
|
|
2645
|
+
* found.
|
|
2646
|
+
*
|
|
2647
|
+
* @param actionElement The DOM node to retrieve the jsaction map from.
|
|
2648
|
+
* @param eventInfo `EventInfo` to set `action` and `actionElement` if an
|
|
2649
|
+
* action is found on the `actionElement`.
|
|
2650
|
+
*/
|
|
2651
|
+
populateActionOnElement(actionElement, eventInfo) {
|
|
2652
|
+
const actionMap = parseActions(actionElement, getContainer(eventInfo));
|
|
2653
|
+
const actionName = actionMap[getEventType(eventInfo)];
|
|
2654
|
+
if (actionName !== undefined) {
|
|
2655
|
+
setAction(eventInfo, actionName, actionElement);
|
|
2656
|
+
}
|
|
2657
|
+
if (this.hasA11yClickSupport) {
|
|
2658
|
+
this.populateClickOnlyAction(actionElement, eventInfo, actionMap);
|
|
2659
|
+
}
|
|
2660
|
+
if (EventContract.A11Y_SUPPORT_IN_DISPATCHER) {
|
|
2661
|
+
if (getEventType(eventInfo) === Attribute$1.MAYBE_CLICK_EVENT_TYPE &&
|
|
2662
|
+
actionMap[EventType.CLICK] !== undefined) {
|
|
2663
|
+
// We'll take the first CLICK action we find and have the dispatcher
|
|
2664
|
+
// check if the keydown event can be used as a CLICK. If not, the
|
|
2665
|
+
// dispatcher will retrigger the event so that we can find a keydown
|
|
2666
|
+
// event instead.
|
|
2667
|
+
// When we get MAYBE_CLICK_EVENT_TYPE as an eventType, we want to
|
|
2668
|
+
// retrieve the action corresponding to CLICK, but still keep the
|
|
2669
|
+
// eventInfoLib.getEventType(eventInfo, ) as MAYBE_CLICK_EVENT_TYPE. The
|
|
2670
|
+
// dispatcher uses this event type to determine if it should get the
|
|
2671
|
+
// handler for the action.
|
|
2672
|
+
setAction(eventInfo, actionMap[EventType.CLICK], actionElement);
|
|
2673
|
+
}
|
|
2674
|
+
else {
|
|
2675
|
+
populateClickOnlyAction(actionElement, eventInfo, actionMap);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Enables jsaction handlers to be called for the event type given by
|
|
2681
|
+
* name.
|
|
2682
|
+
*
|
|
2683
|
+
* If the event is already registered, this does nothing.
|
|
2684
|
+
*
|
|
2685
|
+
* @param prefixedEventType If supplied, this event is used in
|
|
2686
|
+
* the actual browser event registration instead of the name that is
|
|
2687
|
+
* exposed to jsaction. Use this if you e.g. want users to be able
|
|
2688
|
+
* to subscribe to jsaction="transitionEnd:foo" while the underlying
|
|
2689
|
+
* event is webkitTransitionEnd in one browser and mozTransitionEnd
|
|
2690
|
+
* in another.
|
|
2691
|
+
*/
|
|
2692
|
+
addEvent(eventType, prefixedEventType) {
|
|
2693
|
+
if (eventType in this.eventHandlers || !this.containerManager) {
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
if (!EventContract.MOUSE_SPECIAL_SUPPORT &&
|
|
2697
|
+
(eventType === EventType.MOUSEENTER ||
|
|
2698
|
+
eventType === EventType.MOUSELEAVE ||
|
|
2699
|
+
eventType === EventType.POINTERENTER ||
|
|
2700
|
+
eventType === EventType.POINTERLEAVE)) {
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
const eventHandler = (eventType, event, container) => {
|
|
2704
|
+
this.handleEvent(eventType, event, container);
|
|
2705
|
+
};
|
|
2706
|
+
// Store the callback to allow us to replay events.
|
|
2707
|
+
this.eventHandlers[eventType] = eventHandler;
|
|
2708
|
+
const browserEventType = getBrowserEventType(prefixedEventType || eventType);
|
|
2709
|
+
if (browserEventType !== eventType) {
|
|
2710
|
+
const eventTypes = this.browserEventTypeToExtraEventTypes[browserEventType] || [];
|
|
2711
|
+
eventTypes.push(eventType);
|
|
2712
|
+
this.browserEventTypeToExtraEventTypes[browserEventType] = eventTypes;
|
|
2713
|
+
}
|
|
2714
|
+
this.containerManager.addEventListener(browserEventType, (element) => {
|
|
2715
|
+
return (event) => {
|
|
2716
|
+
eventHandler(eventType, event, element);
|
|
2717
|
+
};
|
|
2718
|
+
});
|
|
2719
|
+
// Automatically install a keypress/keydown event handler if support for
|
|
2720
|
+
// accessible clicks is turned on.
|
|
2721
|
+
if (this.addA11yClickListener && eventType === EventType.CLICK) {
|
|
2722
|
+
this.addEvent(EventType.KEYDOWN);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Gets the queued early events and replay them using the appropriate handler
|
|
2727
|
+
* in the provided event contract. Once all the events are replayed, it cleans
|
|
2728
|
+
* up the early contract.
|
|
2729
|
+
*/
|
|
2730
|
+
replayEarlyEvents() {
|
|
2731
|
+
// Check if the early contract is present and prevent calling this function
|
|
2732
|
+
// more than once.
|
|
2733
|
+
const earlyJsactionData = window._ejsa;
|
|
2734
|
+
if (!earlyJsactionData) {
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2737
|
+
// Replay the early contract events.
|
|
2738
|
+
const earlyEventInfos = earlyJsactionData.q;
|
|
2739
|
+
for (let idx = 0; idx < earlyEventInfos.length; idx++) {
|
|
2740
|
+
const earlyEventInfo = earlyEventInfos[idx];
|
|
2741
|
+
const eventTypes = this.getEventTypesForBrowserEventType(earlyEventInfo.eventType);
|
|
2742
|
+
for (let i = 0; i < eventTypes.length; i++) {
|
|
2743
|
+
const eventInfo = cloneEventInfo(earlyEventInfo);
|
|
2744
|
+
// EventInfo eventType maps to JSAction's internal event type,
|
|
2745
|
+
// rather than the browser event type.
|
|
2746
|
+
setEventType(eventInfo, eventTypes[i]);
|
|
2747
|
+
this.handleEventInfo(eventInfo);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
// Clean up the early contract.
|
|
2751
|
+
const earlyEventTypes = earlyJsactionData.et;
|
|
2752
|
+
const earlyEventHandler = earlyJsactionData.h;
|
|
2753
|
+
for (let idx = 0; idx < earlyEventTypes.length; idx++) {
|
|
2754
|
+
const eventType = earlyEventTypes[idx];
|
|
2755
|
+
window.document.documentElement.removeEventListener(eventType, earlyEventHandler);
|
|
2756
|
+
}
|
|
2757
|
+
delete window._ejsa;
|
|
2758
|
+
}
|
|
2759
|
+
/**
|
|
2760
|
+
* Returns all JSAction event types that have been registered for a given
|
|
2761
|
+
* browser event type.
|
|
2762
|
+
*/
|
|
2763
|
+
getEventTypesForBrowserEventType(browserEventType) {
|
|
2764
|
+
const eventTypes = [];
|
|
2765
|
+
if (this.eventHandlers[browserEventType]) {
|
|
2766
|
+
eventTypes.push(browserEventType);
|
|
2767
|
+
}
|
|
2768
|
+
if (this.browserEventTypeToExtraEventTypes[browserEventType]) {
|
|
2769
|
+
eventTypes.push(...this.browserEventTypeToExtraEventTypes[browserEventType]);
|
|
2770
|
+
}
|
|
2771
|
+
return eventTypes;
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Returns the event handler function for a given event type.
|
|
2775
|
+
*/
|
|
2776
|
+
handler(eventType) {
|
|
2777
|
+
return this.eventHandlers[eventType];
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Cleans up the event contract. This resets all of the `EventContract`'s
|
|
2781
|
+
* internal state. Users are responsible for not using this `EventContract`
|
|
2782
|
+
* after it has been cleaned up.
|
|
2783
|
+
*/
|
|
2784
|
+
cleanUp() {
|
|
2785
|
+
this.containerManager.cleanUp();
|
|
2786
|
+
this.containerManager = null;
|
|
2787
|
+
this.eventHandlers = {};
|
|
2788
|
+
this.browserEventTypeToExtraEventTypes = {};
|
|
2789
|
+
this.dispatcher = null;
|
|
2790
|
+
this.queuedEventInfos = [];
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Register a dispatcher function. Event info of each event mapped to
|
|
2794
|
+
* a jsaction is passed for handling to this callback. The queued
|
|
2795
|
+
* events are passed as well to the dispatcher for later replaying
|
|
2796
|
+
* once the dispatcher is registered. Clears the event queue to null.
|
|
2797
|
+
*
|
|
2798
|
+
* @param dispatcher The dispatcher function.
|
|
2799
|
+
* @param restriction
|
|
2800
|
+
*/
|
|
2801
|
+
registerDispatcher(dispatcher, restriction) {
|
|
2802
|
+
this.ecrd(dispatcher, restriction);
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Unrenamed alias for registerDispatcher. Necessary for any codebases that
|
|
2806
|
+
* split the `EventContract` and `Dispatcher` code into different compilation
|
|
2807
|
+
* units.
|
|
2808
|
+
*/
|
|
2809
|
+
ecrd(dispatcher, restriction) {
|
|
2810
|
+
this.dispatcher = dispatcher;
|
|
2811
|
+
if (this.queuedEventInfos?.length) {
|
|
2812
|
+
for (let i = 0; i < this.queuedEventInfos.length; i++) {
|
|
2813
|
+
this.handleEventInfo(this.queuedEventInfos[i]);
|
|
2814
|
+
}
|
|
2815
|
+
this.queuedEventInfos = null;
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Adds a11y click support to the given `EventContract`. Meant to be called in
|
|
2820
|
+
* the same compilation unit as the `EventContract`.
|
|
2821
|
+
*/
|
|
2822
|
+
addA11yClickSupport() {
|
|
2823
|
+
this.addA11yClickSupportImpl(updateEventInfoForA11yClick, preventDefaultForA11yClick, populateClickOnlyAction);
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Enables a11y click support to be deferred. Meant to be called in the same
|
|
2827
|
+
* compilation unit as the `EventContract`.
|
|
2828
|
+
*/
|
|
2829
|
+
exportAddA11yClickSupport() {
|
|
2830
|
+
this.addA11yClickListener = true;
|
|
2831
|
+
this.ecaacs = this.addA11yClickSupportImpl.bind(this);
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Unrenamed function that loads a11yClickSupport.
|
|
2835
|
+
*/
|
|
2836
|
+
addA11yClickSupportImpl(updateEventInfoForA11yClick, preventDefaultForA11yClick, populateClickOnlyAction) {
|
|
2837
|
+
this.addA11yClickListener = true;
|
|
2838
|
+
this.hasA11yClickSupport = true;
|
|
2839
|
+
this.updateEventInfoForA11yClick = updateEventInfoForA11yClick;
|
|
2840
|
+
this.preventDefaultForA11yClick = preventDefaultForA11yClick;
|
|
2841
|
+
this.populateClickOnlyAction = populateClickOnlyAction;
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Adds a11y click support to the given `EventContract`. Meant to be called
|
|
2846
|
+
* in a different compilation unit from the `EventContract`. The `EventContract`
|
|
2847
|
+
* must have called `exportAddA11yClickSupport` in its compilation unit for this
|
|
2848
|
+
* to have any effect.
|
|
2849
|
+
*/
|
|
2850
|
+
function addDeferredA11yClickSupport(eventContract) {
|
|
2851
|
+
eventContract.ecaacs?.(updateEventInfoForA11yClick, preventDefaultForA11yClick, populateClickOnlyAction);
|
|
2852
|
+
}
|
|
2853
|
+
/**
|
|
2854
|
+
* Determines whether or not the `EventContract` needs to check with the
|
|
2855
|
+
* dispatcher even if there's no action.
|
|
2856
|
+
*/
|
|
2857
|
+
function checkDispatcherForA11yClick(eventInfo) {
|
|
2858
|
+
return (EventContract.A11Y_SUPPORT_IN_DISPATCHER &&
|
|
2859
|
+
getEventType(eventInfo) === Attribute$1.MAYBE_CLICK_EVENT_TYPE);
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Returns true if the default action of this event should be prevented before
|
|
2863
|
+
* this event is dispatched.
|
|
2864
|
+
*/
|
|
2865
|
+
function shouldPreventDefaultBeforeDispatching(actionElement, eventInfo) {
|
|
2866
|
+
// Prevent browser from following <a> node links if a jsaction is present
|
|
2867
|
+
// and we are dispatching the action now. Note that the targetElement may be
|
|
2868
|
+
// a child of an anchor that has a jsaction attached. For that reason, we
|
|
2869
|
+
// need to check the actionElement rather than the targetElement.
|
|
2870
|
+
return (actionElement.tagName === 'A' &&
|
|
2871
|
+
(getEventType(eventInfo) === EventType.CLICK ||
|
|
2872
|
+
getEventType(eventInfo) === EventType.CLICKMOD));
|
|
2873
|
+
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Parses and caches an element's jsaction element into a map.
|
|
2876
|
+
*
|
|
2877
|
+
* This is primarily for internal use.
|
|
2878
|
+
*
|
|
2879
|
+
* @param actionElement The DOM node to retrieve the jsaction map from.
|
|
2880
|
+
* @param container The node which limits the namespace lookup for a jsaction
|
|
2881
|
+
* name. The container node itself will not be searched.
|
|
2882
|
+
* @return Map from event to qualified name of the jsaction bound to it.
|
|
2883
|
+
*/
|
|
2884
|
+
function parseActions(actionElement, container) {
|
|
2885
|
+
let actionMap = get(actionElement);
|
|
2886
|
+
if (!actionMap) {
|
|
2887
|
+
const jsactionAttribute = getAttr(actionElement, Attribute.JSACTION);
|
|
2888
|
+
if (!jsactionAttribute) {
|
|
2889
|
+
actionMap = EMPTY_ACTION_MAP;
|
|
2890
|
+
set(actionElement, actionMap);
|
|
2891
|
+
}
|
|
2892
|
+
else {
|
|
2893
|
+
actionMap = getParsed(jsactionAttribute);
|
|
2894
|
+
if (!actionMap) {
|
|
2895
|
+
actionMap = {};
|
|
2896
|
+
const values = jsactionAttribute.split(REGEXP_SEMICOLON);
|
|
2897
|
+
for (let idx = 0; idx < values.length; idx++) {
|
|
2898
|
+
const value = values[idx];
|
|
2899
|
+
if (!value) {
|
|
2900
|
+
continue;
|
|
2901
|
+
}
|
|
2902
|
+
const colon = value.indexOf(Char.EVENT_ACTION_SEPARATOR);
|
|
2903
|
+
const hasColon = colon !== -1;
|
|
2904
|
+
const type = hasColon ? stringTrim(value.substr(0, colon)) : DEFAULT_EVENT_TYPE;
|
|
2905
|
+
const action = hasColon ? stringTrim(value.substr(colon + 1)) : value;
|
|
2906
|
+
actionMap[type] = action;
|
|
2907
|
+
}
|
|
2908
|
+
setParsed(jsactionAttribute, actionMap);
|
|
2909
|
+
}
|
|
2910
|
+
// If namespace support is active we need to augment the (potentially
|
|
2911
|
+
// cached) jsaction mapping with the namespace.
|
|
2912
|
+
if (EventContract.JSNAMESPACE_SUPPORT) {
|
|
2913
|
+
const noNs = actionMap;
|
|
2914
|
+
actionMap = {};
|
|
2915
|
+
for (const type in noNs) {
|
|
2916
|
+
actionMap[type] = getFullyQualifiedAction(noNs[type], actionElement, container);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
set(actionElement, actionMap);
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
return actionMap;
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Returns the fully qualified jsaction action. If the given jsaction
|
|
2926
|
+
* name doesn't already contain the namespace, the function iterates
|
|
2927
|
+
* over ancestor nodes until a jsnamespace attribute is found, and
|
|
2928
|
+
* uses the value of that attribute as the namespace.
|
|
2929
|
+
*
|
|
2930
|
+
* @param action The jsaction action to resolve.
|
|
2931
|
+
* @param start The node from which to start searching for a jsnamespace
|
|
2932
|
+
* attribute.
|
|
2933
|
+
* @param container The node which limits the search for a jsnamespace
|
|
2934
|
+
* attribute. This node will be searched.
|
|
2935
|
+
* @return The fully qualified name of the jsaction. If no namespace is found,
|
|
2936
|
+
* returns the unqualified name in case it exists in the global namespace.
|
|
2937
|
+
*/
|
|
2938
|
+
function getFullyQualifiedAction(action, start, container) {
|
|
2939
|
+
if (EventContract.JSNAMESPACE_SUPPORT) {
|
|
2940
|
+
if (isNamespacedAction(action)) {
|
|
2941
|
+
return action;
|
|
2942
|
+
}
|
|
2943
|
+
let node = start;
|
|
2944
|
+
while (node) {
|
|
2945
|
+
const namespace = getNamespaceFromElement(node);
|
|
2946
|
+
if (namespace) {
|
|
2947
|
+
return namespace + Char.NAMESPACE_ACTION_SEPARATOR + action;
|
|
2948
|
+
}
|
|
2949
|
+
// If this node is the container, stop.
|
|
2950
|
+
if (node === container) {
|
|
2951
|
+
break;
|
|
2952
|
+
}
|
|
2953
|
+
node = node.parentNode;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
return action;
|
|
2957
|
+
}
|
|
2958
|
+
/**
|
|
2959
|
+
* Checks if a jsaction action contains a namespace part.
|
|
2960
|
+
*/
|
|
2961
|
+
function isNamespacedAction(action) {
|
|
2962
|
+
return action.indexOf(Char.NAMESPACE_ACTION_SEPARATOR) >= 0;
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Returns the value of the jsnamespace attribute of the given node.
|
|
2966
|
+
* Also caches the value for subsequent lookups.
|
|
2967
|
+
* @param element The node whose jsnamespace attribute is being asked for.
|
|
2968
|
+
* @return The value of the jsnamespace attribute, or null if not found.
|
|
2969
|
+
*/
|
|
2970
|
+
function getNamespaceFromElement(element) {
|
|
2971
|
+
let namespace = getNamespace(element);
|
|
2972
|
+
// Only query for the attribute if it has not been queried for
|
|
2973
|
+
// before. getAttr() returns null if an attribute is not present. Thus,
|
|
2974
|
+
// namespace is string|null if the query took place in the past, or
|
|
2975
|
+
// undefined if the query did not take place.
|
|
2976
|
+
if (namespace === undefined) {
|
|
2977
|
+
namespace = getAttr(element, Attribute.JSNAMESPACE);
|
|
2978
|
+
setNamespace(element, namespace);
|
|
2979
|
+
}
|
|
2980
|
+
return namespace;
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Accesses the event handler attribute value of a DOM node. It guards
|
|
2984
|
+
* against weird situations (described in the body) that occur in
|
|
2985
|
+
* connection with nodes that are removed from their document.
|
|
2986
|
+
* @param element The DOM element.
|
|
2987
|
+
* @param attribute The name of the attribute to access.
|
|
2988
|
+
* @return The attribute value if it was found, null otherwise.
|
|
2989
|
+
*/
|
|
2990
|
+
function getAttr(element, attribute) {
|
|
2991
|
+
let value = null;
|
|
2992
|
+
// NOTE: Nodes in IE do not always have a getAttribute
|
|
2993
|
+
// method defined. This is the case where sourceElement has in
|
|
2994
|
+
// fact been removed from the DOM before eventContract begins
|
|
2995
|
+
// handling - where a parentNode does not have getAttribute
|
|
2996
|
+
// defined.
|
|
2997
|
+
// NOTE: We must use the 'in' operator instead of the regular dot
|
|
2998
|
+
// notation, since the latter fails in IE8 if the getAttribute method is not
|
|
2999
|
+
// defined. See b/7139109.
|
|
3000
|
+
if ('getAttribute' in element) {
|
|
3001
|
+
value = element.getAttribute(attribute);
|
|
3002
|
+
}
|
|
3003
|
+
return value;
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Helper function to trim whitespace from the beginning and the end
|
|
3007
|
+
* of the string. This deliberately doesn't use the closure equivalent
|
|
3008
|
+
* to keep dependencies small.
|
|
3009
|
+
* @param str Input string.
|
|
3010
|
+
* @return Trimmed string.
|
|
3011
|
+
*/
|
|
3012
|
+
function stringTrim(str) {
|
|
3013
|
+
if (typeof String.prototype.trim === 'function') {
|
|
3014
|
+
return str.trim();
|
|
3015
|
+
}
|
|
3016
|
+
const trimmedLeft = str.replace(/^\s+/, '');
|
|
3017
|
+
return trimmedLeft.replace(/\s+$/, '');
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
/**
|
|
3021
|
+
* Provides a factory function for bootstrapping an event contract on a
|
|
3022
|
+
* window object.
|
|
3023
|
+
* @param field The property on the window that the event contract will be placed on.
|
|
3024
|
+
* @param container The container that listens to events
|
|
3025
|
+
* @param appId A given identifier for an application. If there are multiple apps on the page
|
|
3026
|
+
* then this is how contracts can be initialized for each one.
|
|
3027
|
+
* @param events An array of event names that should be listened to.
|
|
3028
|
+
* @param anyWindow The global window object that should receive the event contract.
|
|
3029
|
+
* @returns The `event` contract. This is both assigned to `anyWindow` and returned for testing.
|
|
3030
|
+
*/
|
|
3031
|
+
function bootstrapEventContract(field, container, appId, events, anyWindow = window) {
|
|
3032
|
+
if (!anyWindow[field]) {
|
|
3033
|
+
anyWindow[field] = {};
|
|
3034
|
+
}
|
|
3035
|
+
const eventContract = new EventContract(new EventContractContainer(container));
|
|
3036
|
+
anyWindow[field][appId] = eventContract;
|
|
3037
|
+
for (const ev of events) {
|
|
3038
|
+
eventContract.addEvent(ev);
|
|
3039
|
+
}
|
|
3040
|
+
return eventContract;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
export { Dispatcher, EventContract, EventContractContainer, EventInfoWrapper, bootstrapEventContract, registerDispatcher };
|
|
3044
|
+
//# sourceMappingURL=event-dispatch.mjs.map
|