event-dispatch.mjs 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545
  1. /**
  2. * @license Angular v19.2.13
  3. * (c) 2010-2025 Google LLC. https://angular.io/
  4. * License: MIT
  5. */
  6. const Attribute = {
  7. /**
  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.
  16. *
  17. * See grammar in README.md for expected syntax in the attribute value.
  18. */
  19. JSACTION: 'jsaction',
  20. };
  21. /** All properties that are used by jsaction. */
  22. const Property = {
  23. /**
  24. * The parsed value of the jsaction attribute is stored in this
  25. * property on the DOM node. The parsed value is an Object. The
  26. * property names of the object are the events; the values are the
  27. * names of the actions. This property is attached even on nodes
  28. * that don't have a jsaction attribute as an optimization, because
  29. * property lookup is faster than attribute access.
  30. */
  31. JSACTION: '__jsaction',
  32. /**
  33. * The owner property references an a logical owner for a DOM node. JSAction
  34. * will follow this reference instead of parentNode when traversing the DOM
  35. * to find jsaction attributes. This allows overlaying a logical structure
  36. * over a document where the DOM structure can't reflect that structure.
  37. */
  38. OWNER: '__owner',
  39. };
  40. /**
  41. * Map from jsaction annotation to a parsed map from event name to action name.
  42. */
  43. const parseCache = {};
  44. /**
  45. * Reads the jsaction parser cache from the given DOM Element.
  46. */
  47. function get(element) {
  48. return element[Property.JSACTION];
  49. }
  50. /**
  51. * Reads the jsaction parser cache for the given DOM element. If no cache is yet present,
  52. * creates an empty one.
  53. */
  54. function getDefaulted(element) {
  55. const cache = get(element) ?? {};
  56. set(element, cache);
  57. return cache;
  58. }
  59. /**
  60. * Writes the jsaction parser cache to the given DOM Element.
  61. */
  62. function set(element, actionMap) {
  63. element[Property.JSACTION] = actionMap;
  64. }
  65. /**
  66. * Looks up the parsed action map from the source jsaction attribute value.
  67. *
  68. * @param text Unparsed jsaction attribute value.
  69. * @return Parsed jsaction attribute value, if already present in the cache.
  70. */
  71. function getParsed(text) {
  72. return parseCache[text];
  73. }
  74. /**
  75. * Inserts the parse result for the given source jsaction value into the cache.
  76. *
  77. * @param text Unparsed jsaction attribute value.
  78. * @param parsed Attribute value parsed into the action map.
  79. */
  80. function setParsed(text, parsed) {
  81. parseCache[text] = parsed;
  82. }
  83. /*
  84. * Names of events that are special to jsaction. These are not all
  85. * event types that are legal to use in either HTML or the addEvent()
  86. * API, but these are the ones that are treated specially. All other
  87. * DOM events can be used in either addEvent() or in the value of the
  88. * jsaction attribute. Beware of browser specific events or events
  89. * that don't bubble though: If they are not mentioned here, then
  90. * event contract doesn't work around their peculiarities.
  91. */
  92. const EventType = {
  93. /**
  94. * The click event. In addEvent() refers to all click events, in the
  95. * jsaction attribute it refers to the unmodified click and Enter/Space
  96. * keypress events. In the latter case, a jsaction click will be triggered,
  97. * for accessibility reasons. See clickmod and clickonly, below.
  98. */
  99. CLICK: 'click',
  100. /**
  101. * Specifies the jsaction for a modified click event (i.e. a mouse
  102. * click with the modifier key Cmd/Ctrl pressed). This event isn't
  103. * separately enabled in addEvent(), because in the DOM, it's just a
  104. * click event.
  105. */
  106. CLICKMOD: 'clickmod',
  107. /**
  108. * The dblclick event.
  109. */
  110. DBLCLICK: 'dblclick',
  111. /**
  112. * Focus doesn't bubble, but you can use it in addEvent() and
  113. * jsaction anyway. EventContract does the right thing under the
  114. * hood.
  115. */
  116. FOCUS: 'focus',
  117. /**
  118. * This event only exists in IE. For addEvent() and jsaction, use
  119. * focus instead; EventContract does the right thing even though
  120. * focus doesn't bubble.
  121. */
  122. FOCUSIN: 'focusin',
  123. /**
  124. * Analog to focus.
  125. */
  126. BLUR: 'blur',
  127. /**
  128. * Analog to focusin.
  129. */
  130. FOCUSOUT: 'focusout',
  131. /**
  132. * Submit doesn't bubble, so it cannot be used with event
  133. * contract. However, the browser helpfully fires a click event on
  134. * the submit button of a form (even if the form is not submitted by
  135. * a click on the submit button). So you should handle click on the
  136. * submit button instead.
  137. */
  138. SUBMIT: 'submit',
  139. /**
  140. * The keydown event. In addEvent() and non-click jsaction it represents the
  141. * regular DOM keydown event. It represents click actions in non-Gecko
  142. * browsers.
  143. */
  144. KEYDOWN: 'keydown',
  145. /**
  146. * The keypress event. In addEvent() and non-click jsaction it represents the
  147. * regular DOM keypress event. It represents click actions in Gecko browsers.
  148. */
  149. KEYPRESS: 'keypress',
  150. /**
  151. * The keyup event. In addEvent() and non-click jsaction it represents the
  152. * regular DOM keyup event. It represents click actions in non-Gecko
  153. * browsers.
  154. */
  155. KEYUP: 'keyup',
  156. /**
  157. * The mouseover event. Can either be used directly or used implicitly to
  158. * capture mouseenter events. In addEvent(), it represents a regular DOM
  159. * mouseover event.
  160. */
  161. MOUSEOVER: 'mouseover',
  162. /**
  163. * The mouseout event. Can either be used directly or used implicitly to
  164. * capture mouseover events. In addEvent(), it represents a regular DOM
  165. * mouseout event.
  166. */
  167. MOUSEOUT: 'mouseout',
  168. /**
  169. * The mouseenter event. Does not bubble and fires individually on each
  170. * element being entered within a DOM tree.
  171. */
  172. MOUSEENTER: 'mouseenter',
  173. /**
  174. * The mouseleave event. Does not bubble and fires individually on each
  175. * element being entered within a DOM tree.
  176. */
  177. MOUSELEAVE: 'mouseleave',
  178. /**
  179. * The pointerover event. Can either be used directly or used implicitly to
  180. * capture pointerenter events. In addEvent(), it represents a regular DOM
  181. * pointerover event.
  182. */
  183. POINTEROVER: 'pointerover',
  184. /**
  185. * The pointerout event. Can either be used directly or used implicitly to
  186. * capture pointerover events. In addEvent(), it represents a regular DOM
  187. * pointerout event.
  188. */
  189. POINTEROUT: 'pointerout',
  190. /**
  191. * The pointerenter event. Does not bubble and fires individually on each
  192. * element being entered within a DOM tree.
  193. */
  194. POINTERENTER: 'pointerenter',
  195. /**
  196. * The pointerleave event. Does not bubble and fires individually on each
  197. * element being entered within a DOM tree.
  198. */
  199. POINTERLEAVE: 'pointerleave',
  200. /**
  201. * The error event. The error event doesn't bubble, but you can use it in
  202. * addEvent() and jsaction anyway. EventContract does the right thing under
  203. * the hood (except in IE8 which does not use error events).
  204. */
  205. ERROR: 'error',
  206. /**
  207. * The load event. The load event doesn't bubble, but you can use it in
  208. * addEvent() and jsaction anyway. EventContract does the right thing
  209. * under the hood.
  210. */
  211. LOAD: 'load',
  212. /**
  213. * The touchstart event. Bubbles, will only ever fire in browsers with
  214. * touch support.
  215. */
  216. TOUCHSTART: 'touchstart',
  217. /**
  218. * The touchend event. Bubbles, will only ever fire in browsers with
  219. * touch support.
  220. */
  221. TOUCHEND: 'touchend',
  222. /**
  223. * The touchmove event. Bubbles, will only ever fire in browsers with
  224. * touch support.
  225. */
  226. TOUCHMOVE: 'touchmove',
  227. /**
  228. * The toggle event. The toggle event doesn't bubble, but you can use it in
  229. * addEvent() and jsaction anyway. EventContract does the right thing
  230. * under the hood.
  231. */
  232. TOGGLE: 'toggle'};
  233. /** All event types that do not bubble or capture and need a polyfill. */
  234. const MOUSE_SPECIAL_EVENT_TYPES = [
  235. EventType.MOUSEENTER,
  236. EventType.MOUSELEAVE,
  237. 'pointerenter',
  238. 'pointerleave',
  239. ];
  240. /** All event types that are registered in the bubble phase. */
  241. const BUBBLE_EVENT_TYPES = [
  242. EventType.CLICK,
  243. EventType.DBLCLICK,
  244. EventType.FOCUSIN,
  245. EventType.FOCUSOUT,
  246. EventType.KEYDOWN,
  247. EventType.KEYUP,
  248. EventType.KEYPRESS,
  249. EventType.MOUSEOVER,
  250. EventType.MOUSEOUT,
  251. EventType.SUBMIT,
  252. EventType.TOUCHSTART,
  253. EventType.TOUCHEND,
  254. EventType.TOUCHMOVE,
  255. 'touchcancel',
  256. 'auxclick',
  257. 'change',
  258. 'compositionstart',
  259. 'compositionupdate',
  260. 'compositionend',
  261. 'beforeinput',
  262. 'input',
  263. 'select',
  264. 'copy',
  265. 'cut',
  266. 'paste',
  267. 'mousedown',
  268. 'mouseup',
  269. 'wheel',
  270. 'contextmenu',
  271. 'dragover',
  272. 'dragenter',
  273. 'dragleave',
  274. 'drop',
  275. 'dragstart',
  276. 'dragend',
  277. 'pointerdown',
  278. 'pointermove',
  279. 'pointerup',
  280. 'pointercancel',
  281. 'pointerover',
  282. 'pointerout',
  283. 'gotpointercapture',
  284. 'lostpointercapture',
  285. // Video events.
  286. 'ended',
  287. 'loadedmetadata',
  288. // Page visibility events.
  289. 'pagehide',
  290. 'pageshow',
  291. 'visibilitychange',
  292. // Content visibility events.
  293. 'beforematch',
  294. ];
  295. /** All event types that are registered in the capture phase. */
  296. const CAPTURE_EVENT_TYPES = [
  297. EventType.FOCUS,
  298. EventType.BLUR,
  299. EventType.ERROR,
  300. EventType.LOAD,
  301. EventType.TOGGLE,
  302. ];
  303. /**
  304. * Whether or not an event type should be registered in the capture phase.
  305. * @param eventType
  306. * @returns bool
  307. */
  308. const isCaptureEventType = (eventType) => CAPTURE_EVENT_TYPES.indexOf(eventType) >= 0;
  309. /** All event types that are registered early. */
  310. const EARLY_EVENT_TYPES = BUBBLE_EVENT_TYPES.concat(CAPTURE_EVENT_TYPES);
  311. /**
  312. * Whether or not an event type is registered in the early contract.
  313. */
  314. const isEarlyEventType = (eventType) => EARLY_EVENT_TYPES.indexOf(eventType) >= 0;
  315. /**
  316. * Gets a browser event type, if it would differ from the JSAction event type.
  317. */
  318. function getBrowserEventType(eventType) {
  319. // Mouseenter and mouseleave events are not handled directly because they
  320. // are not available everywhere. In browsers where they are available, they
  321. // don't bubble and aren't visible at the container boundary. Instead, we
  322. // synthesize the mouseenter and mouseleave events from mouseover and
  323. // mouseout events, respectively. Cf. eventcontract.js.
  324. if (eventType === EventType.MOUSEENTER) {
  325. return EventType.MOUSEOVER;
  326. }
  327. else if (eventType === EventType.MOUSELEAVE) {
  328. return EventType.MOUSEOUT;
  329. }
  330. else if (eventType === EventType.POINTERENTER) {
  331. return EventType.POINTEROVER;
  332. }
  333. else if (eventType === EventType.POINTERLEAVE) {
  334. return EventType.POINTEROUT;
  335. }
  336. return eventType;
  337. }
  338. /**
  339. * Registers the event handler function with the given DOM element for
  340. * the given event type.
  341. *
  342. * @param element The element.
  343. * @param eventType The event type.
  344. * @param handler The handler function to install.
  345. * @param passive A boolean value that, if `true`, indicates that the function
  346. * specified by `handler` will never call `preventDefault()`.
  347. * @return Information needed to uninstall the event handler eventually.
  348. */
  349. function addEventListener(element, eventType, handler, passive) {
  350. // All event handlers are registered in the bubbling
  351. // phase.
  352. //
  353. // All browsers support focus and blur, but these events only are propagated
  354. // in the capture phase. Very legacy browsers do not support focusin or
  355. // focusout.
  356. //
  357. // It would be a bad idea to register all event handlers in the
  358. // capture phase because then regular onclick handlers would not be
  359. // executed at all on events that trigger a jsaction. That's not
  360. // entirely what we want, at least for now.
  361. //
  362. // Error and load events (i.e. on images) do not bubble so they are also
  363. // handled in the capture phase.
  364. let capture = false;
  365. if (isCaptureEventType(eventType)) {
  366. capture = true;
  367. }
  368. const options = typeof passive === 'boolean' ? { capture, passive } : capture;
  369. element.addEventListener(eventType, handler, options);
  370. return { eventType, handler, capture, passive };
  371. }
  372. /**
  373. * Removes the event handler for the given event from the element.
  374. * the given event type.
  375. *
  376. * @param element The element.
  377. * @param info The information needed to deregister the handler, as returned by
  378. * addEventListener(), above.
  379. */
  380. function removeEventListener(element, info) {
  381. if (element.removeEventListener) {
  382. // It's worth noting that some browser releases have been inconsistent on this, and unless
  383. // you have specific reasons otherwise, it's probably wise to use the same values used for
  384. // the call to addEventListener() when calling removeEventListener().
  385. const options = typeof info.passive === 'boolean' ? { capture: info.capture } : info.capture;
  386. element.removeEventListener(info.eventType, info.handler, options);
  387. // `detachEvent` is an old DOM API.
  388. }
  389. else if (element.detachEvent) {
  390. // `detachEvent` is an old DOM API.
  391. element.detachEvent(`on${info.eventType}`, info.handler);
  392. }
  393. }
  394. /**
  395. * Prevents the default action of an event.
  396. * @param e The event to prevent the default action for.
  397. */
  398. function preventDefault(e) {
  399. e.preventDefault ? e.preventDefault() : (e.returnValue = false);
  400. }
  401. /**
  402. * Whether we are on a Mac. Not pulling in useragent just for this.
  403. */
  404. let isMac = typeof navigator !== 'undefined' && /Macintosh/.test(navigator.userAgent);
  405. /**
  406. * Determines and returns whether the given event (which is assumed to be a
  407. * click event) is a middle click.
  408. * NOTE: There is not a consistent way to identify middle click
  409. * http://www.unixpapa.com/js/mouse.html
  410. */
  411. function isMiddleClick(e) {
  412. return (
  413. // `which` is an old DOM API.
  414. e.which === 2 ||
  415. // `which` is an old DOM API.
  416. (e.which == null &&
  417. // `button` is an old DOM API.
  418. e.button === 4) // middle click for IE
  419. );
  420. }
  421. /**
  422. * Determines and returns whether the given event (which is assumed
  423. * to be a click event) is modified. A middle click is considered a modified
  424. * click to retain the default browser action, which opens a link in a new tab.
  425. * @param e The event.
  426. * @return Whether the given event is modified.
  427. */
  428. function isModifiedClickEvent(e) {
  429. return (
  430. // `metaKey` is an old DOM API.
  431. (isMac && e.metaKey) ||
  432. // `ctrlKey` is an old DOM API.
  433. (!isMac && e.ctrlKey) ||
  434. isMiddleClick(e) ||
  435. // `shiftKey` is an old DOM API.
  436. e.shiftKey);
  437. }
  438. /**
  439. * Determines whether the event corresponds to a non-bubbling mouse
  440. * event type (mouseenter, mouseleave, pointerenter, and pointerleave).
  441. *
  442. * During mouseover (mouseenter) and pointerover (pointerenter), the
  443. * relatedTarget is the element being entered from. During mouseout (mouseleave)
  444. * and pointerout (pointerleave), the relatedTarget is the element being exited
  445. * to.
  446. *
  447. * In both cases, if relatedTarget is outside target, then the corresponding
  448. * special event has occurred, otherwise it hasn't.
  449. *
  450. * @param e The mouseover/mouseout event.
  451. * @param type The type of the mouse special event.
  452. * @param element The element on which the jsaction for the
  453. * mouseenter/mouseleave event is defined.
  454. * @return True if the event is a mouseenter/mouseleave event.
  455. */
  456. function isMouseSpecialEvent(e, type, element) {
  457. // `relatedTarget` is an old DOM API.
  458. const related = e.relatedTarget;
  459. return (((e.type === EventType.MOUSEOVER && type === EventType.MOUSEENTER) ||
  460. (e.type === EventType.MOUSEOUT && type === EventType.MOUSELEAVE) ||
  461. (e.type === EventType.POINTEROVER && type === EventType.POINTERENTER) ||
  462. (e.type === EventType.POINTEROUT && type === EventType.POINTERLEAVE)) &&
  463. (!related || (related !== element && !element.contains(related))));
  464. }
  465. /**
  466. * Creates a new EventLike object for a mouseenter/mouseleave event that's
  467. * derived from the original corresponding mouseover/mouseout event.
  468. * @param e The event.
  469. * @param target The element on which the jsaction for the mouseenter/mouseleave
  470. * event is defined.
  471. * @return A modified event-like object copied from the event object passed into
  472. * this function.
  473. */
  474. function createMouseSpecialEvent(e, target) {
  475. // We have to create a copy of the event object because we need to mutate
  476. // its fields. We do this for the special mouse events because the event
  477. // target needs to be retargeted to the action element rather than the real
  478. // element (since we are simulating the special mouse events with mouseover/
  479. // mouseout).
  480. //
  481. // Since we're making a copy anyways, we might as well attempt to convert
  482. // this event into a pseudo-real mouseenter/mouseleave event by adjusting
  483. // its type.
  484. //
  485. const copy = {};
  486. for (const property in e) {
  487. if (property === 'srcElement' || property === 'target') {
  488. continue;
  489. }
  490. const key = property;
  491. // Making a copy requires iterating through all properties of `Event`.
  492. const value = e[key];
  493. if (typeof value === 'function') {
  494. continue;
  495. }
  496. // Value should be the expected type, but the value of `key` is not known
  497. // statically.
  498. copy[key] = value;
  499. }
  500. if (e.type === EventType.MOUSEOVER) {
  501. copy['type'] = EventType.MOUSEENTER;
  502. }
  503. else if (e.type === EventType.MOUSEOUT) {
  504. copy['type'] = EventType.MOUSELEAVE;
  505. }
  506. else if (e.type === EventType.POINTEROVER) {
  507. copy['type'] = EventType.POINTERENTER;
  508. }
  509. else {
  510. copy['type'] = EventType.POINTERLEAVE;
  511. }
  512. copy['target'] = copy['srcElement'] = target;
  513. copy['bubbles'] = false;
  514. copy['_originalEvent'] = e;
  515. return copy;
  516. }
  517. /**
  518. * Whether the user agent is running on iOS.
  519. */
  520. const isIos = typeof navigator !== 'undefined' && /iPhone|iPad|iPod/.test(navigator.userAgent);
  521. /**
  522. * A class representing a container node and all the event handlers
  523. * installed on it. Used so that handlers can be cleaned up if the
  524. * container is removed from the contract.
  525. */
  526. class EventContractContainer {
  527. element;
  528. /**
  529. * Array of event handlers and their corresponding event types that are
  530. * installed on this container.
  531. *
  532. */
  533. handlerInfos = [];
  534. /**
  535. * @param element The container Element.
  536. */
  537. constructor(element) {
  538. this.element = element;
  539. }
  540. /**
  541. * Installs the provided installer on the element owned by this container,
  542. * and maintains a reference to resulting handler in order to remove it
  543. * later if desired.
  544. */
  545. addEventListener(eventType, getHandler, passive) {
  546. // In iOS, event bubbling doesn't happen automatically in any DOM element,
  547. // unless it has an onclick attribute or DOM event handler attached to it.
  548. // This breaks JsAction in some cases. See "Making Elements Clickable"
  549. // section at http://goo.gl/2VoGnB.
  550. //
  551. // A workaround for this issue is to change the CSS cursor style to 'pointer'
  552. // for the container element, which magically turns on event bubbling. This
  553. // solution is described in the comments section at http://goo.gl/6pEO1z.
  554. //
  555. // We use a navigator.userAgent check here as this problem is present both
  556. // on Mobile Safari and thin WebKit wrappers, such as Chrome for iOS.
  557. if (isIos) {
  558. this.element.style.cursor = 'pointer';
  559. }
  560. this.handlerInfos.push(addEventListener(this.element, eventType, getHandler(this.element), passive));
  561. }
  562. /**
  563. * Removes all the handlers installed on this container.
  564. */
  565. cleanUp() {
  566. for (let i = 0; i < this.handlerInfos.length; i++) {
  567. removeEventListener(this.element, this.handlerInfos[i]);
  568. }
  569. this.handlerInfos = [];
  570. }
  571. }
  572. const Char = {
  573. /**
  574. * The separator between the event name and action in the jsaction
  575. * attribute value.
  576. */
  577. EVENT_ACTION_SEPARATOR: ':',
  578. };
  579. /** Added for readability when accessing stable property names. */
  580. function getEventType(eventInfo) {
  581. return eventInfo.eventType;
  582. }
  583. /** Added for readability when accessing stable property names. */
  584. function setEventType(eventInfo, eventType) {
  585. eventInfo.eventType = eventType;
  586. }
  587. /** Added for readability when accessing stable property names. */
  588. function getEvent(eventInfo) {
  589. return eventInfo.event;
  590. }
  591. /** Added for readability when accessing stable property names. */
  592. function setEvent(eventInfo, event) {
  593. eventInfo.event = event;
  594. }
  595. /** Added for readability when accessing stable property names. */
  596. function getTargetElement(eventInfo) {
  597. return eventInfo.targetElement;
  598. }
  599. /** Added for readability when accessing stable property names. */
  600. function setTargetElement(eventInfo, targetElement) {
  601. eventInfo.targetElement = targetElement;
  602. }
  603. /** Added for readability when accessing stable property names. */
  604. function getContainer(eventInfo) {
  605. return eventInfo.eic;
  606. }
  607. /** Added for readability when accessing stable property names. */
  608. function setContainer(eventInfo, container) {
  609. eventInfo.eic = container;
  610. }
  611. /** Added for readability when accessing stable property names. */
  612. function getTimestamp(eventInfo) {
  613. return eventInfo.timeStamp;
  614. }
  615. /** Added for readability when accessing stable property names. */
  616. function setTimestamp(eventInfo, timestamp) {
  617. eventInfo.timeStamp = timestamp;
  618. }
  619. /** Added for readability when accessing stable property names. */
  620. function getAction(eventInfo) {
  621. return eventInfo.eia;
  622. }
  623. /** Added for readability when accessing stable property names. */
  624. function setAction(eventInfo, actionName, actionElement) {
  625. eventInfo.eia = [actionName, actionElement];
  626. }
  627. /** Added for readability when accessing stable property names. */
  628. function unsetAction(eventInfo) {
  629. eventInfo.eia = undefined;
  630. }
  631. /** Added for readability when accessing stable property names. */
  632. function getActionElement(actionInfo) {
  633. return actionInfo[1];
  634. }
  635. /** Added for readability when accessing stable property names. */
  636. function getIsReplay(eventInfo) {
  637. return eventInfo.eirp;
  638. }
  639. /** Added for readability when accessing stable property names. */
  640. function setIsReplay(eventInfo, replay) {
  641. eventInfo.eirp = replay;
  642. }
  643. /** Added for readability when accessing stable property names. */
  644. function getResolved(eventInfo) {
  645. return eventInfo.eir;
  646. }
  647. /** Added for readability when accessing stable property names. */
  648. function setResolved(eventInfo, resolved) {
  649. eventInfo.eir = resolved;
  650. }
  651. /** Clones an `EventInfo` */
  652. function cloneEventInfo(eventInfo) {
  653. return {
  654. eventType: eventInfo.eventType,
  655. event: eventInfo.event,
  656. targetElement: eventInfo.targetElement,
  657. eic: eventInfo.eic,
  658. eia: eventInfo.eia,
  659. timeStamp: eventInfo.timeStamp,
  660. eirp: eventInfo.eirp,
  661. eiack: eventInfo.eiack,
  662. eir: eventInfo.eir,
  663. };
  664. }
  665. /**
  666. * Utility function for creating an `EventInfo`.
  667. *
  668. * This can be used from code-size sensitive compilation units, as taking
  669. * parameters vs. an `Object` literal reduces code size.
  670. */
  671. function createEventInfoFromParameters(eventType, event, targetElement, container, timestamp, action, isReplay, a11yClickKey) {
  672. return {
  673. eventType,
  674. event,
  675. targetElement,
  676. eic: container,
  677. timeStamp: timestamp,
  678. eia: action,
  679. eirp: isReplay,
  680. eiack: a11yClickKey,
  681. };
  682. }
  683. /**
  684. * Utility class around an `EventInfo`.
  685. *
  686. * This should be used in compilation units that are less sensitive to code
  687. * size.
  688. */
  689. class EventInfoWrapper {
  690. eventInfo;
  691. constructor(eventInfo) {
  692. this.eventInfo = eventInfo;
  693. }
  694. getEventType() {
  695. return getEventType(this.eventInfo);
  696. }
  697. setEventType(eventType) {
  698. setEventType(this.eventInfo, eventType);
  699. }
  700. getEvent() {
  701. return getEvent(this.eventInfo);
  702. }
  703. setEvent(event) {
  704. setEvent(this.eventInfo, event);
  705. }
  706. getTargetElement() {
  707. return getTargetElement(this.eventInfo);
  708. }
  709. setTargetElement(targetElement) {
  710. setTargetElement(this.eventInfo, targetElement);
  711. }
  712. getContainer() {
  713. return getContainer(this.eventInfo);
  714. }
  715. setContainer(container) {
  716. setContainer(this.eventInfo, container);
  717. }
  718. getTimestamp() {
  719. return getTimestamp(this.eventInfo);
  720. }
  721. setTimestamp(timestamp) {
  722. setTimestamp(this.eventInfo, timestamp);
  723. }
  724. getAction() {
  725. const action = getAction(this.eventInfo);
  726. if (!action)
  727. return undefined;
  728. return {
  729. name: action[0],
  730. element: action[1],
  731. };
  732. }
  733. setAction(action) {
  734. if (!action) {
  735. unsetAction(this.eventInfo);
  736. return;
  737. }
  738. setAction(this.eventInfo, action.name, action.element);
  739. }
  740. getIsReplay() {
  741. return getIsReplay(this.eventInfo);
  742. }
  743. setIsReplay(replay) {
  744. setIsReplay(this.eventInfo, replay);
  745. }
  746. getResolved() {
  747. return getResolved(this.eventInfo);
  748. }
  749. setResolved(resolved) {
  750. setResolved(this.eventInfo, resolved);
  751. }
  752. clone() {
  753. return new EventInfoWrapper(cloneEventInfo(this.eventInfo));
  754. }
  755. }
  756. /**
  757. * Since maps from event to action are immutable we can use a single map
  758. * to represent the empty map.
  759. */
  760. const EMPTY_ACTION_MAP = {};
  761. /**
  762. * This regular expression matches a semicolon.
  763. */
  764. const REGEXP_SEMICOLON = /\s*;\s*/;
  765. /** If no event type is defined, defaults to `click`. */
  766. const DEFAULT_EVENT_TYPE = EventType.CLICK;
  767. /** Resolves actions for Events. */
  768. class ActionResolver {
  769. a11yClickSupport = false;
  770. clickModSupport = true;
  771. syntheticMouseEventSupport;
  772. updateEventInfoForA11yClick = undefined;
  773. preventDefaultForA11yClick = undefined;
  774. populateClickOnlyAction = undefined;
  775. constructor({ syntheticMouseEventSupport = false, clickModSupport = true, } = {}) {
  776. this.syntheticMouseEventSupport = syntheticMouseEventSupport;
  777. this.clickModSupport = clickModSupport;
  778. }
  779. resolveEventType(eventInfo) {
  780. // We distinguish modified and plain clicks in order to support the
  781. // default browser behavior of modified clicks on links; usually to
  782. // open the URL of the link in new tab or new window on ctrl/cmd
  783. // click. A DOM 'click' event is mapped to the jsaction 'click'
  784. // event iff there is no modifier present on the event. If there is
  785. // a modifier, it's mapped to 'clickmod' instead.
  786. //
  787. // It's allowed to omit the event in the jsaction attribute. In that
  788. // case, 'click' is assumed. Thus the following two are equivalent:
  789. //
  790. // <a href="someurl" jsaction="gna.fu">
  791. // <a href="someurl" jsaction="click:gna.fu">
  792. //
  793. // For unmodified clicks, EventContract invokes the jsaction
  794. // 'gna.fu'. For modified clicks, EventContract won't find a
  795. // suitable action and leave the event to be handled by the
  796. // browser.
  797. //
  798. // In order to also invoke a jsaction handler for a modifier click,
  799. // 'clickmod' needs to be used:
  800. //
  801. // <a href="someurl" jsaction="clickmod:gna.fu">
  802. //
  803. // EventContract invokes the jsaction 'gna.fu' for modified
  804. // clicks. Unmodified clicks are left to the browser.
  805. //
  806. // In order to set up the event contract to handle both clickonly and
  807. // clickmod, only addEvent(EventType.CLICK) is necessary.
  808. //
  809. // In order to set up the event contract to handle click,
  810. // addEvent() is necessary for CLICK, KEYDOWN, and KEYPRESS event types. If
  811. // a11y click support is enabled, addEvent() will set up the appropriate key
  812. // event handler automatically.
  813. if (this.clickModSupport &&
  814. getEventType(eventInfo) === EventType.CLICK &&
  815. isModifiedClickEvent(getEvent(eventInfo))) {
  816. setEventType(eventInfo, EventType.CLICKMOD);
  817. }
  818. else if (this.a11yClickSupport) {
  819. this.updateEventInfoForA11yClick(eventInfo);
  820. }
  821. }
  822. resolveAction(eventInfo) {
  823. if (getResolved(eventInfo)) {
  824. return;
  825. }
  826. this.populateAction(eventInfo, getTargetElement(eventInfo));
  827. setResolved(eventInfo, true);
  828. }
  829. resolveParentAction(eventInfo) {
  830. const action = getAction(eventInfo);
  831. const actionElement = action && getActionElement(action);
  832. unsetAction(eventInfo);
  833. const parentNode = actionElement && this.getParentNode(actionElement);
  834. if (!parentNode) {
  835. return;
  836. }
  837. this.populateAction(eventInfo, parentNode);
  838. }
  839. /**
  840. * Searches for a jsaction that the DOM event maps to and creates an
  841. * object containing event information used for dispatching by
  842. * jsaction.Dispatcher. This method populates the `action` and `actionElement`
  843. * fields of the EventInfo object passed in by finding the first
  844. * jsaction attribute above the target Node of the event, and below
  845. * the container Node, that specifies a jsaction for the event
  846. * type. If no such jsaction is found, then action is undefined.
  847. *
  848. * @param eventInfo `EventInfo` to set `action` and `actionElement` if an
  849. * action is found on any `Element` in the path of the `Event`.
  850. */
  851. populateAction(eventInfo, currentTarget) {
  852. let actionElement = currentTarget;
  853. while (actionElement && actionElement !== getContainer(eventInfo)) {
  854. if (actionElement.nodeType === Node.ELEMENT_NODE) {
  855. this.populateActionOnElement(actionElement, eventInfo);
  856. }
  857. if (getAction(eventInfo)) {
  858. // An event is handled by at most one jsaction. Thus we stop at the
  859. // first matching jsaction specified in a jsaction attribute up the
  860. // ancestor chain of the event target node.
  861. break;
  862. }
  863. actionElement = this.getParentNode(actionElement);
  864. }
  865. const action = getAction(eventInfo);
  866. if (!action) {
  867. // No action found.
  868. return;
  869. }
  870. if (this.a11yClickSupport) {
  871. this.preventDefaultForA11yClick(eventInfo);
  872. }
  873. // We attempt to handle the mouseenter/mouseleave events here by
  874. // detecting whether the mouseover/mouseout events correspond to
  875. // entering/leaving an element.
  876. if (this.syntheticMouseEventSupport) {
  877. if (getEventType(eventInfo) === EventType.MOUSEENTER ||
  878. getEventType(eventInfo) === EventType.MOUSELEAVE ||
  879. getEventType(eventInfo) === EventType.POINTERENTER ||
  880. getEventType(eventInfo) === EventType.POINTERLEAVE) {
  881. // We attempt to handle the mouseenter/mouseleave events here by
  882. // detecting whether the mouseover/mouseout events correspond to
  883. // entering/leaving an element.
  884. if (isMouseSpecialEvent(getEvent(eventInfo), getEventType(eventInfo), getActionElement(action))) {
  885. // If both mouseover/mouseout and mouseenter/mouseleave events are
  886. // enabled, two separate handlers for mouseover/mouseout are
  887. // registered. Both handlers will see the same event instance
  888. // so we create a copy to avoid interfering with the dispatching of
  889. // the mouseover/mouseout event.
  890. const copiedEvent = createMouseSpecialEvent(getEvent(eventInfo), getActionElement(action));
  891. setEvent(eventInfo, copiedEvent);
  892. // Since the mouseenter/mouseleave events do not bubble, the target
  893. // of the event is technically the `actionElement` (the node with the
  894. // `jsaction` attribute)
  895. setTargetElement(eventInfo, getActionElement(action));
  896. }
  897. else {
  898. unsetAction(eventInfo);
  899. }
  900. }
  901. }
  902. }
  903. /**
  904. * Walk to the parent node, unless the node has a different owner in
  905. * which case we walk to the owner. Attempt to walk to host of a
  906. * shadow root if needed.
  907. */
  908. getParentNode(element) {
  909. const owner = element[Property.OWNER];
  910. if (owner) {
  911. return owner;
  912. }
  913. const parentNode = element.parentNode;
  914. if (parentNode?.nodeName === '#document-fragment') {
  915. return parentNode?.host ?? null;
  916. }
  917. return parentNode;
  918. }
  919. /**
  920. * Accesses the jsaction map on a node and retrieves the name of the
  921. * action the given event is mapped to, if any. It parses the
  922. * attribute value and stores it in a property on the node for
  923. * subsequent retrieval without re-parsing and re-accessing the
  924. * attribute.
  925. *
  926. * @param actionElement The DOM node to retrieve the jsaction map from.
  927. * @param eventInfo `EventInfo` to set `action` and `actionElement` if an
  928. * action is found on the `actionElement`.
  929. */
  930. populateActionOnElement(actionElement, eventInfo) {
  931. const actionMap = this.parseActions(actionElement);
  932. const actionName = actionMap[getEventType(eventInfo)];
  933. if (actionName !== undefined) {
  934. setAction(eventInfo, actionName, actionElement);
  935. }
  936. if (this.a11yClickSupport) {
  937. this.populateClickOnlyAction(actionElement, eventInfo, actionMap);
  938. }
  939. }
  940. /**
  941. * Parses and caches an element's jsaction element into a map.
  942. *
  943. * This is primarily for internal use.
  944. *
  945. * @param actionElement The DOM node to retrieve the jsaction map from.
  946. * @return Map from event to qualified name of the jsaction bound to it.
  947. */
  948. parseActions(actionElement) {
  949. let actionMap = get(actionElement);
  950. if (!actionMap) {
  951. const jsactionAttribute = actionElement.getAttribute(Attribute.JSACTION);
  952. if (!jsactionAttribute) {
  953. actionMap = EMPTY_ACTION_MAP;
  954. set(actionElement, actionMap);
  955. }
  956. else {
  957. actionMap = getParsed(jsactionAttribute);
  958. if (!actionMap) {
  959. actionMap = {};
  960. const values = jsactionAttribute.split(REGEXP_SEMICOLON);
  961. for (let idx = 0; idx < values.length; idx++) {
  962. const value = values[idx];
  963. if (!value) {
  964. continue;
  965. }
  966. const colon = value.indexOf(Char.EVENT_ACTION_SEPARATOR);
  967. const hasColon = colon !== -1;
  968. const type = hasColon ? value.substr(0, colon).trim() : DEFAULT_EVENT_TYPE;
  969. const action = hasColon ? value.substr(colon + 1).trim() : value;
  970. actionMap[type] = action;
  971. }
  972. setParsed(jsactionAttribute, actionMap);
  973. }
  974. set(actionElement, actionMap);
  975. }
  976. }
  977. return actionMap;
  978. }
  979. addA11yClickSupport(updateEventInfoForA11yClick, preventDefaultForA11yClick, populateClickOnlyAction) {
  980. this.a11yClickSupport = true;
  981. this.updateEventInfoForA11yClick = updateEventInfoForA11yClick;
  982. this.preventDefaultForA11yClick = preventDefaultForA11yClick;
  983. this.populateClickOnlyAction = populateClickOnlyAction;
  984. }
  985. }
  986. /**
  987. * @fileoverview An enum to control who can call certain jsaction APIs.
  988. */
  989. var Restriction;
  990. (function (Restriction) {
  991. Restriction[Restriction["I_AM_THE_JSACTION_FRAMEWORK"] = 0] = "I_AM_THE_JSACTION_FRAMEWORK";
  992. })(Restriction || (Restriction = {}));
  993. /**
  994. * Receives a DOM event, determines the jsaction associated with the source
  995. * element of the DOM event, and invokes the handler associated with the
  996. * jsaction.
  997. */
  998. class Dispatcher {
  999. dispatchDelegate;
  1000. // The ActionResolver to use to resolve actions.
  1001. actionResolver;
  1002. /** The replayer function to be called when there are queued events. */
  1003. eventReplayer;
  1004. /** Whether the event replay is scheduled. */
  1005. eventReplayScheduled = false;
  1006. /** The queue of events. */
  1007. replayEventInfoWrappers = [];
  1008. /**
  1009. * Options are:
  1010. * - `eventReplayer`: When the event contract dispatches replay events
  1011. * to the Dispatcher, the Dispatcher collects them and in the next tick
  1012. * dispatches them to the `eventReplayer`. Defaults to dispatching to `dispatchDelegate`.
  1013. * @param dispatchDelegate A function that should handle dispatching an `EventInfoWrapper` to handlers.
  1014. */
  1015. constructor(dispatchDelegate, { actionResolver, eventReplayer, } = {}) {
  1016. this.dispatchDelegate = dispatchDelegate;
  1017. this.actionResolver = actionResolver;
  1018. this.eventReplayer = eventReplayer;
  1019. }
  1020. /**
  1021. * Receives an event or the event queue from the EventContract. The event
  1022. * queue is copied and it attempts to replay.
  1023. * If event info is passed in it looks for an action handler that can handle
  1024. * the given event. If there is no handler registered queues the event and
  1025. * checks if a loader is registered for the given namespace. If so, calls it.
  1026. *
  1027. * Alternatively, if in global dispatch mode, calls all registered global
  1028. * handlers for the appropriate event type.
  1029. *
  1030. * The three functionalities of this call are deliberately not split into
  1031. * three methods (and then declared as an abstract interface), because the
  1032. * interface is used by EventContract, which lives in a different jsbinary.
  1033. * Therefore the interface between the three is defined entirely in terms that
  1034. * are invariant under jscompiler processing (Function and Array, as opposed
  1035. * to a custom type with method names).
  1036. *
  1037. * @param eventInfo The info for the event that triggered this call or the
  1038. * queue of events from EventContract.
  1039. */
  1040. dispatch(eventInfo) {
  1041. const eventInfoWrapper = new EventInfoWrapper(eventInfo);
  1042. this.actionResolver?.resolveEventType(eventInfo);
  1043. this.actionResolver?.resolveAction(eventInfo);
  1044. const action = eventInfoWrapper.getAction();
  1045. if (action && shouldPreventDefaultBeforeDispatching(action.element, eventInfoWrapper)) {
  1046. preventDefault(eventInfoWrapper.getEvent());
  1047. }
  1048. if (this.eventReplayer && eventInfoWrapper.getIsReplay()) {
  1049. this.scheduleEventInfoWrapperReplay(eventInfoWrapper);
  1050. return;
  1051. }
  1052. this.dispatchDelegate(eventInfoWrapper);
  1053. }
  1054. /**
  1055. * Schedules an `EventInfoWrapper` for replay. The replaying will happen in its own
  1056. * stack once the current flow cedes control. This is done to mimic
  1057. * browser event handling.
  1058. */
  1059. scheduleEventInfoWrapperReplay(eventInfoWrapper) {
  1060. this.replayEventInfoWrappers.push(eventInfoWrapper);
  1061. if (this.eventReplayScheduled) {
  1062. return;
  1063. }
  1064. this.eventReplayScheduled = true;
  1065. Promise.resolve().then(() => {
  1066. this.eventReplayScheduled = false;
  1067. this.eventReplayer(this.replayEventInfoWrappers);
  1068. });
  1069. }
  1070. }
  1071. /**
  1072. * Returns true if the default action of this event should be prevented before
  1073. * this event is dispatched.
  1074. */
  1075. function shouldPreventDefaultBeforeDispatching(actionElement, eventInfoWrapper) {
  1076. // Prevent browser from following <a> node links if a jsaction is present
  1077. // and we are dispatching the action now. Note that the targetElement may be
  1078. // a child of an anchor that has a jsaction attached. For that reason, we
  1079. // need to check the actionElement rather than the targetElement.
  1080. return (actionElement.tagName === 'A' &&
  1081. (eventInfoWrapper.getEventType() === EventType.CLICK ||
  1082. eventInfoWrapper.getEventType() === EventType.CLICKMOD));
  1083. }
  1084. /** An internal symbol used to indicate whether propagation should be stopped or not. */
  1085. const PROPAGATION_STOPPED_SYMBOL =
  1086. /* @__PURE__ */ Symbol.for('propagationStopped');
  1087. /** Extra event phases beyond what the browser provides. */
  1088. const EventPhase = {
  1089. REPLAY: 101,
  1090. };
  1091. const PREVENT_DEFAULT_ERROR_MESSAGE_DETAILS = ' Because event replay occurs after browser dispatch, `preventDefault` would have no ' +
  1092. 'effect. You can check whether an event is being replayed by accessing the event phase: ' +
  1093. '`event.eventPhase === EventPhase.REPLAY`.';
  1094. const PREVENT_DEFAULT_ERROR_MESSAGE = `\`preventDefault\` called during event replay.`;
  1095. const COMPOSED_PATH_ERROR_MESSAGE_DETAILS = ' Because event replay occurs after browser ' +
  1096. 'dispatch, `composedPath()` will be empty. Iterate parent nodes from `event.target` or ' +
  1097. '`event.currentTarget` if you need to check elements in the event path.';
  1098. const COMPOSED_PATH_ERROR_MESSAGE = `\`composedPath\` called during event replay.`;
  1099. /**
  1100. * A dispatcher that uses browser-based `Event` semantics, for example bubbling, `stopPropagation`,
  1101. * `currentTarget`, etc.
  1102. */
  1103. class EventDispatcher {
  1104. dispatchDelegate;
  1105. clickModSupport;
  1106. actionResolver;
  1107. dispatcher;
  1108. constructor(dispatchDelegate, clickModSupport = true) {
  1109. this.dispatchDelegate = dispatchDelegate;
  1110. this.clickModSupport = clickModSupport;
  1111. this.actionResolver = new ActionResolver({ clickModSupport });
  1112. this.dispatcher = new Dispatcher((eventInfoWrapper) => {
  1113. this.dispatchToDelegate(eventInfoWrapper);
  1114. }, {
  1115. actionResolver: this.actionResolver,
  1116. });
  1117. }
  1118. /**
  1119. * The entrypoint for the `EventContract` dispatch.
  1120. */
  1121. dispatch(eventInfo) {
  1122. this.dispatcher.dispatch(eventInfo);
  1123. }
  1124. /** Internal method that does basic disaptching. */
  1125. dispatchToDelegate(eventInfoWrapper) {
  1126. if (eventInfoWrapper.getIsReplay()) {
  1127. prepareEventForReplay(eventInfoWrapper);
  1128. }
  1129. prepareEventForBubbling(eventInfoWrapper);
  1130. while (eventInfoWrapper.getAction()) {
  1131. prepareEventForDispatch(eventInfoWrapper);
  1132. // If this is a capture event, ONLY dispatch if the action element is the target.
  1133. if (isCaptureEventType(eventInfoWrapper.getEventType()) &&
  1134. eventInfoWrapper.getAction().element !== eventInfoWrapper.getTargetElement()) {
  1135. return;
  1136. }
  1137. this.dispatchDelegate(eventInfoWrapper.getEvent(), eventInfoWrapper.getAction().name);
  1138. if (propagationStopped(eventInfoWrapper)) {
  1139. return;
  1140. }
  1141. this.actionResolver.resolveParentAction(eventInfoWrapper.eventInfo);
  1142. }
  1143. }
  1144. }
  1145. function prepareEventForBubbling(eventInfoWrapper) {
  1146. const event = eventInfoWrapper.getEvent();
  1147. const originalStopPropagation = eventInfoWrapper.getEvent().stopPropagation.bind(event);
  1148. const stopPropagation = () => {
  1149. event[PROPAGATION_STOPPED_SYMBOL] = true;
  1150. originalStopPropagation();
  1151. };
  1152. patchEventInstance(event, 'stopPropagation', stopPropagation);
  1153. patchEventInstance(event, 'stopImmediatePropagation', stopPropagation);
  1154. }
  1155. function propagationStopped(eventInfoWrapper) {
  1156. const event = eventInfoWrapper.getEvent();
  1157. return !!event[PROPAGATION_STOPPED_SYMBOL];
  1158. }
  1159. function prepareEventForReplay(eventInfoWrapper) {
  1160. const event = eventInfoWrapper.getEvent();
  1161. const target = eventInfoWrapper.getTargetElement();
  1162. const originalPreventDefault = event.preventDefault.bind(event);
  1163. patchEventInstance(event, 'target', target);
  1164. patchEventInstance(event, 'eventPhase', EventPhase.REPLAY);
  1165. patchEventInstance(event, 'preventDefault', () => {
  1166. originalPreventDefault();
  1167. throw new Error(PREVENT_DEFAULT_ERROR_MESSAGE + (ngDevMode ? PREVENT_DEFAULT_ERROR_MESSAGE_DETAILS : ''));
  1168. });
  1169. patchEventInstance(event, 'composedPath', () => {
  1170. throw new Error(COMPOSED_PATH_ERROR_MESSAGE + (ngDevMode ? COMPOSED_PATH_ERROR_MESSAGE_DETAILS : ''));
  1171. });
  1172. }
  1173. function prepareEventForDispatch(eventInfoWrapper) {
  1174. const event = eventInfoWrapper.getEvent();
  1175. const currentTarget = eventInfoWrapper.getAction()?.element;
  1176. if (currentTarget) {
  1177. patchEventInstance(event, 'currentTarget', currentTarget, {
  1178. // `currentTarget` is going to get reassigned every dispatch.
  1179. configurable: true,
  1180. });
  1181. }
  1182. }
  1183. /**
  1184. * Patch `Event` instance during non-standard `Event` dispatch. This patches just the `Event`
  1185. * instance that the browser created, it does not patch global properties or methods.
  1186. *
  1187. * This is necessary because dispatching an `Event` outside of browser dispatch results in
  1188. * incorrect properties and methods that need to be polyfilled or do not work.
  1189. *
  1190. * JSAction dispatch adds two extra "phases" to event dispatch:
  1191. * 1. Event delegation - the event is being dispatched by a delegating event handler on a container
  1192. * (typically `window.document.documentElement`), to a delegated event handler on some child
  1193. * element. Certain `Event` properties will be unintuitive, such as `currentTarget`, which would
  1194. * be the container rather than the child element. Bubbling would also not work. In order to
  1195. * emulate the browser, these properties and methods on the `Event` are patched.
  1196. * 2. Event replay - the event is being dispatched by the framework once the handlers have been
  1197. * loaded (during hydration, or late-loaded). Certain `Event` properties can be unset by the
  1198. * browser because the `Event` is no longer actively being dispatched, such as `target`. Other
  1199. * methods have no effect because the `Event` has already been dispatched, such as
  1200. * `preventDefault`. Bubbling would also not work. These properties and methods are patched,
  1201. * either to fill in information that the browser may have removed, or to throw errors in methods
  1202. * that no longer behave as expected.
  1203. */
  1204. function patchEventInstance(event, property, value, { configurable = false } = {}) {
  1205. Object.defineProperty(event, property, { value, configurable });
  1206. }
  1207. /**
  1208. * Registers deferred functionality for an EventContract and a Jsaction
  1209. * Dispatcher.
  1210. */
  1211. function registerDispatcher$1(eventContract, dispatcher) {
  1212. eventContract.ecrd((eventInfo) => {
  1213. dispatcher.dispatch(eventInfo);
  1214. }, Restriction.I_AM_THE_JSACTION_FRAMEWORK);
  1215. }
  1216. /** Creates an `EarlyJsactionData` object. */
  1217. function createEarlyJsactionData(container) {
  1218. const q = [];
  1219. const d = (eventInfo) => {
  1220. q.push(eventInfo);
  1221. };
  1222. const h = (event) => {
  1223. d(createEventInfoFromParameters(event.type, event, event.target, container, Date.now()));
  1224. };
  1225. return {
  1226. c: container,
  1227. q,
  1228. et: [],
  1229. etc: [],
  1230. d,
  1231. h,
  1232. };
  1233. }
  1234. /** Add all the events to the container stored in the `EarlyJsactionData`. */
  1235. function addEvents(earlyJsactionData, types, capture) {
  1236. for (let i = 0; i < types.length; i++) {
  1237. const eventType = types[i];
  1238. const eventTypes = capture ? earlyJsactionData.etc : earlyJsactionData.et;
  1239. eventTypes.push(eventType);
  1240. earlyJsactionData.c.addEventListener(eventType, earlyJsactionData.h, capture);
  1241. }
  1242. }
  1243. /** Get the queued `EventInfo` objects that were dispatched before a dispatcher was registered. */
  1244. function getQueuedEventInfos(earlyJsactionData) {
  1245. return earlyJsactionData?.q ?? [];
  1246. }
  1247. /** Register a different dispatcher function on the `EarlyJsactionData`. */
  1248. function registerDispatcher(earlyJsactionData, dispatcher) {
  1249. if (!earlyJsactionData) {
  1250. return;
  1251. }
  1252. earlyJsactionData.d = dispatcher;
  1253. }
  1254. /** Removes all event listener handlers. */
  1255. function removeAllEventListeners(earlyJsactionData) {
  1256. if (!earlyJsactionData) {
  1257. return;
  1258. }
  1259. removeEventListeners(earlyJsactionData.c, earlyJsactionData.et, earlyJsactionData.h);
  1260. removeEventListeners(earlyJsactionData.c, earlyJsactionData.etc, earlyJsactionData.h, true);
  1261. }
  1262. function removeEventListeners(container, eventTypes, earlyEventHandler, capture) {
  1263. for (let i = 0; i < eventTypes.length; i++) {
  1264. container.removeEventListener(eventTypes[i], earlyEventHandler, /* useCapture */ capture);
  1265. }
  1266. }
  1267. /**
  1268. * @define Support for the non-bubbling mouseenter and mouseleave events. This
  1269. * flag can be overridden in a build rule.
  1270. */
  1271. const MOUSE_SPECIAL_SUPPORT = false;
  1272. /**
  1273. * @fileoverview Implements the local event handling contract. This
  1274. * allows DOM objects in a container that enters into this contract to
  1275. * define event handlers which are executed in a local context.
  1276. *
  1277. * One EventContract instance can manage the contract for multiple
  1278. * containers, which are added using the addContainer() method.
  1279. *
  1280. * Events can be registered using the addEvent() method.
  1281. *
  1282. * A Dispatcher is added using the registerDispatcher() method. Until there is
  1283. * a dispatcher, events are queued. The idea is that the EventContract
  1284. * class is inlined in the HTML of the top level page and instantiated
  1285. * right after the start of <body>. The Dispatcher class is contained
  1286. * in the external deferred js, and instantiated and registered with
  1287. * EventContract when the external javascript in the page loads. The
  1288. * external javascript will also register the jsaction handlers, which
  1289. * then pick up the queued events at the time of registration.
  1290. *
  1291. * Since this class is meant to be inlined in the main page HTML, the
  1292. * size of the binary compiled from this file MUST be kept as small as
  1293. * possible and thus its dependencies to a minimum.
  1294. */
  1295. /**
  1296. * EventContract intercepts events in the bubbling phase at the
  1297. * boundary of a container element, and maps them to generic actions
  1298. * which are specified using the custom jsaction attribute in
  1299. * HTML. Behavior of the application is then specified in terms of
  1300. * handler for such actions, cf. jsaction.Dispatcher in dispatcher.js.
  1301. *
  1302. * This has several benefits: (1) No DOM event handlers need to be
  1303. * registered on the specific elements in the UI. (2) The set of
  1304. * events that the application has to handle can be specified in terms
  1305. * of the semantics of the application, rather than in terms of DOM
  1306. * events. (3) Invocation of handlers can be delayed and handlers can
  1307. * be delay loaded in a generic way.
  1308. */
  1309. class EventContract {
  1310. static MOUSE_SPECIAL_SUPPORT = MOUSE_SPECIAL_SUPPORT;
  1311. containerManager;
  1312. /**
  1313. * The DOM events which this contract covers. Used to prevent double
  1314. * registration of event types. The value of the map is the
  1315. * internally created DOM event handler function that handles the
  1316. * DOM events. See addEvent().
  1317. *
  1318. */
  1319. eventHandlers = {};
  1320. browserEventTypeToExtraEventTypes = {};
  1321. /**
  1322. * The dispatcher function. Events are passed to this function for
  1323. * handling once it was set using the registerDispatcher() method. This is
  1324. * done because the function is passed from another jsbinary, so passing the
  1325. * instance and invoking the method here would require to leave the method
  1326. * unobfuscated.
  1327. */
  1328. dispatcher = null;
  1329. /**
  1330. * The list of suspended `EventInfo` that will be dispatched
  1331. * as soon as the `Dispatcher` is registered.
  1332. */
  1333. queuedEventInfos = [];
  1334. constructor(containerManager) {
  1335. this.containerManager = containerManager;
  1336. }
  1337. handleEvent(eventType, event, container) {
  1338. const eventInfo = createEventInfoFromParameters(
  1339. /* eventType= */ eventType,
  1340. /* event= */ event,
  1341. /* targetElement= */ event.target,
  1342. /* container= */ container,
  1343. /* timestamp= */ Date.now());
  1344. this.handleEventInfo(eventInfo);
  1345. }
  1346. /**
  1347. * Handle an `EventInfo`.
  1348. */
  1349. handleEventInfo(eventInfo) {
  1350. if (!this.dispatcher) {
  1351. // All events are queued when the dispatcher isn't yet loaded.
  1352. setIsReplay(eventInfo, true);
  1353. this.queuedEventInfos?.push(eventInfo);
  1354. return;
  1355. }
  1356. this.dispatcher(eventInfo);
  1357. }
  1358. /**
  1359. * Enables jsaction handlers to be called for the event type given by
  1360. * name.
  1361. *
  1362. * If the event is already registered, this does nothing.
  1363. *
  1364. * @param prefixedEventType If supplied, this event is used in
  1365. * the actual browser event registration instead of the name that is
  1366. * exposed to jsaction. Use this if you e.g. want users to be able
  1367. * to subscribe to jsaction="transitionEnd:foo" while the underlying
  1368. * event is webkitTransitionEnd in one browser and mozTransitionEnd
  1369. * in another.
  1370. *
  1371. * @param passive A boolean value that, if `true`, indicates that the event
  1372. * handler will never call `preventDefault()`.
  1373. */
  1374. addEvent(eventType, prefixedEventType, passive) {
  1375. if (eventType in this.eventHandlers || !this.containerManager) {
  1376. return;
  1377. }
  1378. if (!EventContract.MOUSE_SPECIAL_SUPPORT && MOUSE_SPECIAL_EVENT_TYPES.indexOf(eventType) >= 0) {
  1379. return;
  1380. }
  1381. const eventHandler = (eventType, event, container) => {
  1382. this.handleEvent(eventType, event, container);
  1383. };
  1384. // Store the callback to allow us to replay events.
  1385. this.eventHandlers[eventType] = eventHandler;
  1386. const browserEventType = getBrowserEventType(prefixedEventType || eventType);
  1387. if (browserEventType !== eventType) {
  1388. const eventTypes = this.browserEventTypeToExtraEventTypes[browserEventType] || [];
  1389. eventTypes.push(eventType);
  1390. this.browserEventTypeToExtraEventTypes[browserEventType] = eventTypes;
  1391. }
  1392. this.containerManager.addEventListener(browserEventType, (element) => {
  1393. return (event) => {
  1394. eventHandler(eventType, event, element);
  1395. };
  1396. }, passive);
  1397. }
  1398. /**
  1399. * Gets the queued early events and replay them using the appropriate handler
  1400. * in the provided event contract. Once all the events are replayed, it cleans
  1401. * up the early contract.
  1402. */
  1403. replayEarlyEvents(earlyJsactionData = window._ejsa) {
  1404. // Check if the early contract is present and prevent calling this function
  1405. // more than once.
  1406. if (!earlyJsactionData) {
  1407. return;
  1408. }
  1409. // Replay the early contract events.
  1410. this.replayEarlyEventInfos(earlyJsactionData.q);
  1411. // Clean up the early contract.
  1412. removeAllEventListeners(earlyJsactionData);
  1413. delete window._ejsa;
  1414. }
  1415. /**
  1416. * Replays all the early `EventInfo` objects, dispatching them through the normal
  1417. * `EventContract` flow.
  1418. */
  1419. replayEarlyEventInfos(earlyEventInfos) {
  1420. for (let i = 0; i < earlyEventInfos.length; i++) {
  1421. const earlyEventInfo = earlyEventInfos[i];
  1422. const eventTypes = this.getEventTypesForBrowserEventType(earlyEventInfo.eventType);
  1423. for (let j = 0; j < eventTypes.length; j++) {
  1424. const eventInfo = cloneEventInfo(earlyEventInfo);
  1425. // EventInfo eventType maps to JSAction's internal event type,
  1426. // rather than the browser event type.
  1427. setEventType(eventInfo, eventTypes[j]);
  1428. this.handleEventInfo(eventInfo);
  1429. }
  1430. }
  1431. }
  1432. /**
  1433. * Returns all JSAction event types that have been registered for a given
  1434. * browser event type.
  1435. */
  1436. getEventTypesForBrowserEventType(browserEventType) {
  1437. const eventTypes = [];
  1438. if (this.eventHandlers[browserEventType]) {
  1439. eventTypes.push(browserEventType);
  1440. }
  1441. if (this.browserEventTypeToExtraEventTypes[browserEventType]) {
  1442. eventTypes.push(...this.browserEventTypeToExtraEventTypes[browserEventType]);
  1443. }
  1444. return eventTypes;
  1445. }
  1446. /**
  1447. * Returns the event handler function for a given event type.
  1448. */
  1449. handler(eventType) {
  1450. return this.eventHandlers[eventType];
  1451. }
  1452. /**
  1453. * Cleans up the event contract. This resets all of the `EventContract`'s
  1454. * internal state. Users are responsible for not using this `EventContract`
  1455. * after it has been cleaned up.
  1456. */
  1457. cleanUp() {
  1458. this.containerManager?.cleanUp();
  1459. this.containerManager = null;
  1460. this.eventHandlers = {};
  1461. this.browserEventTypeToExtraEventTypes = {};
  1462. this.dispatcher = null;
  1463. this.queuedEventInfos = [];
  1464. }
  1465. /**
  1466. * Register a dispatcher function. Event info of each event mapped to
  1467. * a jsaction is passed for handling to this callback. The queued
  1468. * events are passed as well to the dispatcher for later replaying
  1469. * once the dispatcher is registered. Clears the event queue to null.
  1470. *
  1471. * @param dispatcher The dispatcher function.
  1472. * @param restriction
  1473. */
  1474. registerDispatcher(dispatcher, restriction) {
  1475. this.ecrd(dispatcher, restriction);
  1476. }
  1477. /**
  1478. * Unrenamed alias for registerDispatcher. Necessary for any codebases that
  1479. * split the `EventContract` and `Dispatcher` code into different compilation
  1480. * units.
  1481. */
  1482. ecrd(dispatcher, restriction) {
  1483. this.dispatcher = dispatcher;
  1484. if (this.queuedEventInfos?.length) {
  1485. for (let i = 0; i < this.queuedEventInfos.length; i++) {
  1486. this.handleEventInfo(this.queuedEventInfos[i]);
  1487. }
  1488. this.queuedEventInfos = null;
  1489. }
  1490. }
  1491. }
  1492. /**
  1493. * Creates an `EarlyJsactionData`, adds events to it, and populates it on a nested object on
  1494. * the window.
  1495. */
  1496. function bootstrapAppScopedEarlyEventContract(container, appId, bubbleEventTypes, captureEventTypes, dataContainer = window) {
  1497. const earlyJsactionData = createEarlyJsactionData(container);
  1498. if (!dataContainer._ejsas) {
  1499. dataContainer._ejsas = {};
  1500. }
  1501. dataContainer._ejsas[appId] = earlyJsactionData;
  1502. addEvents(earlyJsactionData, bubbleEventTypes);
  1503. addEvents(earlyJsactionData, captureEventTypes, /* capture= */ true);
  1504. }
  1505. /** Get the queued `EventInfo` objects that were dispatched before a dispatcher was registered. */
  1506. function getAppScopedQueuedEventInfos(appId, dataContainer = window) {
  1507. return getQueuedEventInfos(dataContainer._ejsas?.[appId]);
  1508. }
  1509. /**
  1510. * Registers a dispatcher function on the `EarlyJsactionData` present on the nested object on the
  1511. * window.
  1512. */
  1513. function registerAppScopedDispatcher(restriction, appId, dispatcher, dataContainer = window) {
  1514. registerDispatcher(dataContainer._ejsas?.[appId], dispatcher);
  1515. }
  1516. /** Removes all event listener handlers. */
  1517. function removeAllAppScopedEventListeners(appId, dataContainer = window) {
  1518. removeAllEventListeners(dataContainer._ejsas?.[appId]);
  1519. }
  1520. /** Clear the early event contract. */
  1521. function clearAppScopedEarlyEventContract(appId, dataContainer = window) {
  1522. if (!dataContainer._ejsas) {
  1523. return;
  1524. }
  1525. dataContainer._ejsas[appId] = undefined;
  1526. }
  1527. export { Attribute, EventContract, EventContractContainer, EventDispatcher, EventInfoWrapper, EventPhase, bootstrapAppScopedEarlyEventContract, clearAppScopedEarlyEventContract, getDefaulted as getActionCache, getAppScopedQueuedEventInfos, isCaptureEventType, isEarlyEventType, registerAppScopedDispatcher, registerDispatcher$1 as registerDispatcher, removeAllAppScopedEventListeners };
  1528. //# sourceMappingURL=event-dispatch.mjs.map