@angular/core 18.0.0-rc.3 → 18.0.1

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