@angular/core 18.0.0-rc.0 → 18.0.0-rc.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 (57) hide show
  1. package/esm2022/primitives/event-dispatch/index.mjs +2 -2
  2. package/esm2022/primitives/event-dispatch/src/action_resolver.mjs +234 -0
  3. package/esm2022/primitives/event-dispatch/src/base_dispatcher.mjs +6 -8
  4. package/esm2022/primitives/event-dispatch/src/custom_events.mjs +40 -1
  5. package/esm2022/primitives/event-dispatch/src/dispatcher.mjs +22 -15
  6. package/esm2022/primitives/event-dispatch/src/earlyeventcontract.mjs +14 -8
  7. package/esm2022/primitives/event-dispatch/src/event_contract_defines.mjs +1 -14
  8. package/esm2022/primitives/event-dispatch/src/eventcontract.mjs +23 -344
  9. package/esm2022/primitives/event-dispatch/src/register_events.mjs +29 -10
  10. package/esm2022/src/application/application_ref.mjs +6 -3
  11. package/esm2022/src/application/create_application.mjs +3 -3
  12. package/esm2022/src/change_detection/scheduling/exhaustive_check_no_changes.mjs +150 -0
  13. package/esm2022/src/change_detection/scheduling/ng_zone_scheduling.mjs +17 -3
  14. package/esm2022/src/change_detection/scheduling/zoneless_scheduling_impl.mjs +5 -5
  15. package/esm2022/src/core.mjs +2 -1
  16. package/esm2022/src/core_private_export.mjs +2 -1
  17. package/esm2022/src/defer/instructions.mjs +20 -12
  18. package/esm2022/src/defer/interfaces.mjs +1 -3
  19. package/esm2022/src/errors.mjs +1 -1
  20. package/esm2022/src/hydration/event_replay.mjs +23 -3
  21. package/esm2022/src/platform/platform_ref.mjs +8 -1
  22. package/esm2022/src/render3/after_render_hooks.mjs +4 -2
  23. package/esm2022/src/render3/component_ref.mjs +1 -1
  24. package/esm2022/src/render3/instructions/change_detection.mjs +13 -10
  25. package/esm2022/src/render3/instructions/listener.mjs +12 -1
  26. package/esm2022/src/render3/state.mjs +14 -4
  27. package/esm2022/src/render3/view_ref.mjs +3 -2
  28. package/esm2022/src/util/callback_scheduler.mjs +12 -26
  29. package/esm2022/src/version.mjs +1 -1
  30. package/esm2022/src/zone/ng_zone.mjs +9 -23
  31. package/esm2022/testing/src/component_fixture.mjs +1 -3
  32. package/esm2022/testing/src/defer.mjs +1 -2
  33. package/esm2022/testing/src/logger.mjs +3 -3
  34. package/esm2022/testing/src/test_bed_compiler.mjs +3 -4
  35. package/event-dispatch-contract.min.js +1 -1
  36. package/fesm2022/core.mjs +752 -574
  37. package/fesm2022/core.mjs.map +1 -1
  38. package/fesm2022/primitives/event-dispatch.mjs +315 -369
  39. package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
  40. package/fesm2022/primitives/signals.mjs +1 -1
  41. package/fesm2022/rxjs-interop.mjs +1 -1
  42. package/fesm2022/testing.mjs +3 -7
  43. package/fesm2022/testing.mjs.map +1 -1
  44. package/index.d.ts +34 -4
  45. package/package.json +1 -1
  46. package/primitives/event-dispatch/index.d.ts +41 -44
  47. package/primitives/signals/index.d.ts +1 -1
  48. package/rxjs-interop/index.d.ts +1 -1
  49. package/schematics/migrations/http-providers/bundle.js +66 -44
  50. package/schematics/migrations/http-providers/bundle.js.map +2 -2
  51. package/schematics/migrations/invalid-two-way-bindings/bundle.js +39 -9
  52. package/schematics/migrations/invalid-two-way-bindings/bundle.js.map +2 -2
  53. package/schematics/ng-generate/control-flow-migration/bundle.js +39 -9
  54. package/schematics/ng-generate/control-flow-migration/bundle.js.map +2 -2
  55. package/schematics/ng-generate/standalone-migration/bundle.js +48 -18
  56. package/schematics/ng-generate/standalone-migration/bundle.js.map +2 -2
  57. package/testing/index.d.ts +1 -4
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v18.0.0-rc.0
2
+ * @license Angular v18.0.0-rc.1
3
3
  * (c) 2010-2024 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
@@ -244,20 +244,18 @@ class BaseDispatcher {
244
244
  *
245
245
  * @param eventInfo The info for the event that triggered this call or the
246
246
  * queue of events from EventContract.
247
- * @param isGlobalDispatch If true, dispatches a global event instead of a
248
- * regular jsaction handler.
249
247
  */
250
- dispatch(eventInfo, isGlobalDispatch) {
248
+ dispatch(eventInfo) {
251
249
  const eventInfoWrapper = new EventInfoWrapper(eventInfo);
252
250
  if (eventInfoWrapper.getIsReplay()) {
253
- if (isGlobalDispatch || !this.eventReplayer) {
251
+ if (!this.eventReplayer) {
254
252
  return;
255
253
  }
256
254
  this.queueEventInfoWrapper(eventInfoWrapper);
257
255
  this.scheduleEventReplay();
258
256
  return;
259
257
  }
260
- this.dispatchDelegate(eventInfoWrapper, isGlobalDispatch);
258
+ this.dispatchDelegate(eventInfoWrapper);
261
259
  }
262
260
  /** Queue an `EventInfoWrapper` for replay. */
263
261
  queueEventInfoWrapper(eventInfoWrapper) {
@@ -286,8 +284,8 @@ class BaseDispatcher {
286
284
  * Dispatcher.
287
285
  */
288
286
  function registerDispatcher$1(eventContract, dispatcher) {
289
- eventContract.ecrd((eventInfo, globalDispatch) => {
290
- dispatcher.dispatch(eventInfo, globalDispatch);
287
+ eventContract.ecrd((eventInfo) => {
288
+ dispatcher.dispatch(eventInfo);
291
289
  }, Restriction.I_AM_THE_JSACTION_FRAMEWORK);
292
290
  }
293
291
 
@@ -1264,8 +1262,8 @@ class Dispatcher {
1264
1262
  /** A map of global event handlers, where each key is an event type. */
1265
1263
  this.globalHandlers = new Map();
1266
1264
  this.eventReplayer = eventReplayer;
1267
- this.baseDispatcher = new BaseDispatcher((eventInfoWrapper, isGlobalDispatch) => {
1268
- this.dispatchToHandler(eventInfoWrapper, isGlobalDispatch);
1265
+ this.baseDispatcher = new BaseDispatcher((eventInfoWrapper) => {
1266
+ this.dispatchToHandler(eventInfoWrapper);
1269
1267
  }, {
1270
1268
  eventReplayer: (eventInfoWrappers) => {
1271
1269
  this.eventReplayer?.(eventInfoWrappers, this);
@@ -1292,38 +1290,45 @@ class Dispatcher {
1292
1290
  *
1293
1291
  * @param eventInfo The info for the event that triggered this call or the
1294
1292
  * queue of events from EventContract.
1295
- * @param isGlobalDispatch If true, dispatches a global event instead of a
1296
- * regular jsaction handler.
1297
1293
  */
1298
1294
  dispatch(eventInfo, isGlobalDispatch) {
1299
- this.baseDispatcher.dispatch(eventInfo, isGlobalDispatch);
1295
+ this.baseDispatcher.dispatch(eventInfo);
1300
1296
  }
1301
1297
  /**
1302
1298
  * Dispatches an `EventInfoWrapper`.
1303
1299
  */
1304
- dispatchToHandler(eventInfoWrapper, isGlobalDispatch) {
1305
- if (isGlobalDispatch) {
1300
+ dispatchToHandler(eventInfoWrapper) {
1301
+ if (this.globalHandlers.size) {
1302
+ const globalEventInfoWrapper = eventInfoWrapper.clone();
1303
+ // In some cases, `populateAction` will rewrite `click` events to
1304
+ // `clickonly`. Revert back to a regular click, otherwise we won't be able
1305
+ // to execute global event handlers registered on click events.
1306
+ if (globalEventInfoWrapper.getEventType() === EventType.CLICKONLY) {
1307
+ globalEventInfoWrapper.setEventType(EventType.CLICK);
1308
+ }
1306
1309
  // Skip everything related to jsaction handlers, and execute the global
1307
1310
  // handlers.
1308
- const ev = eventInfoWrapper.getEvent();
1309
- const eventTypeHandlers = this.globalHandlers.get(eventInfoWrapper.getEventType());
1311
+ const event = globalEventInfoWrapper.getEvent();
1312
+ const eventTypeHandlers = this.globalHandlers.get(globalEventInfoWrapper.getEventType());
1310
1313
  let shouldPreventDefault = false;
1311
1314
  if (eventTypeHandlers) {
1312
1315
  for (const handler of eventTypeHandlers) {
1313
- if (handler(ev) === false) {
1316
+ if (handler(event) === false) {
1314
1317
  shouldPreventDefault = true;
1315
1318
  }
1316
1319
  }
1317
1320
  }
1318
1321
  if (shouldPreventDefault) {
1319
- preventDefault(ev);
1322
+ preventDefault(event);
1320
1323
  }
1324
+ }
1325
+ const action = eventInfoWrapper.getAction();
1326
+ if (!action) {
1321
1327
  return;
1322
1328
  }
1323
1329
  if (this.stopPropagation) {
1324
1330
  stopPropagation(eventInfoWrapper);
1325
1331
  }
1326
- const action = eventInfoWrapper.getAction();
1327
1332
  let handler = undefined;
1328
1333
  if (this.getHandler) {
1329
1334
  handler = this.getHandler(eventInfoWrapper);
@@ -1475,8 +1480,8 @@ function stopPropagation(eventInfoWrapper) {
1475
1480
  * Dispatcher.
1476
1481
  */
1477
1482
  function registerDispatcher(eventContract, dispatcher) {
1478
- eventContract.ecrd((eventInfo, globalDispatch) => {
1479
- dispatcher.dispatch(eventInfo, globalDispatch);
1483
+ eventContract.ecrd((eventInfo) => {
1484
+ dispatcher.dispatch(eventInfo);
1480
1485
  }, Restriction.I_AM_THE_JSACTION_FRAMEWORK);
1481
1486
  }
1482
1487
 
@@ -1759,59 +1764,6 @@ function clearNamespace(element) {
1759
1764
  }
1760
1765
  }
1761
1766
 
1762
- /**
1763
- * @define Support for jsnamespace attribute. This flag can be overridden in a
1764
- * build rule to trim down the EventContract's binary size.
1765
- */
1766
- const JSNAMESPACE_SUPPORT = true;
1767
- /**
1768
- * @define Support for accessible click actions. This flag can be overridden in
1769
- * a build rule.
1770
- */
1771
- const A11Y_CLICK_SUPPORT = false;
1772
- /**
1773
- * @define Support for the non-bubbling mouseenter and mouseleave events. This
1774
- * flag can be overridden in a build rule.
1775
- */
1776
- const MOUSE_SPECIAL_SUPPORT = false;
1777
- /**
1778
- * @define Call stopPropagation on handled events. When integrating with
1779
- * non-jsaction event handler based code, you will likely want to turn this flag
1780
- * off. While most event handlers will continue to work, jsaction binds focus
1781
- * and blur events in the capture phase and thus with stopPropagation, none of
1782
- * your non-jsaction-handlers will ever see it.
1783
- */
1784
- const STOP_PROPAGATION = true;
1785
- /**
1786
- * @define Support for custom events, which are type EventType.CUSTOM. These are
1787
- * native DOM events with an additional type field and an optional payload.
1788
- */
1789
- const CUSTOM_EVENT_SUPPORT = false;
1790
-
1791
- /**
1792
- * @fileoverview Implements the local event handling contract. This
1793
- * allows DOM objects in a container that enters into this contract to
1794
- * define event handlers which are executed in a local context.
1795
- *
1796
- * One EventContract instance can manage the contract for multiple
1797
- * containers, which are added using the addContainer() method.
1798
- *
1799
- * Events can be registered using the addEvent() method.
1800
- *
1801
- * A Dispatcher is added using the registerDispatcher() method. Until there is
1802
- * a dispatcher, events are queued. The idea is that the EventContract
1803
- * class is inlined in the HTML of the top level page and instantiated
1804
- * right after the start of <body>. The Dispatcher class is contained
1805
- * in the external deferred js, and instantiated and registered with
1806
- * EventContract when the external javascript in the page loads. The
1807
- * external javascript will also register the jsaction handlers, which
1808
- * then pick up the queued events at the time of registration.
1809
- *
1810
- * Since this class is meant to be inlined in the main page HTML, the
1811
- * size of the binary compiled from this file MUST be kept as small as
1812
- * possible and thus its dependencies to a minimum.
1813
- */
1814
- const DEFAULT_EVENT_TYPE = EventType.CLICK;
1815
1767
  /**
1816
1768
  * Since maps from event to action are immutable we can use a single map
1817
1769
  * to represent the empty map.
@@ -1821,114 +1773,32 @@ const EMPTY_ACTION_MAP = {};
1821
1773
  * This regular expression matches a semicolon.
1822
1774
  */
1823
1775
  const REGEXP_SEMICOLON = /\s*;\s*/;
1824
- /**
1825
- * EventContract intercepts events in the bubbling phase at the
1826
- * boundary of a container element, and maps them to generic actions
1827
- * which are specified using the custom jsaction attribute in
1828
- * HTML. Behavior of the application is then specified in terms of
1829
- * handler for such actions, cf. jsaction.Dispatcher in dispatcher.js.
1830
- *
1831
- * This has several benefits: (1) No DOM event handlers need to be
1832
- * registered on the specific elements in the UI. (2) The set of
1833
- * events that the application has to handle can be specified in terms
1834
- * of the semantics of the application, rather than in terms of DOM
1835
- * events. (3) Invocation of handlers can be delayed and handlers can
1836
- * be delay loaded in a generic way.
1837
- */
1838
- class EventContract {
1839
- static { this.CUSTOM_EVENT_SUPPORT = CUSTOM_EVENT_SUPPORT; }
1840
- static { this.STOP_PROPAGATION = STOP_PROPAGATION; }
1841
- static { this.A11Y_CLICK_SUPPORT = A11Y_CLICK_SUPPORT; }
1842
- static { this.MOUSE_SPECIAL_SUPPORT = MOUSE_SPECIAL_SUPPORT; }
1843
- static { this.JSNAMESPACE_SUPPORT = JSNAMESPACE_SUPPORT; }
1844
- constructor(containerManager) {
1845
- /**
1846
- * The DOM events which this contract covers. Used to prevent double
1847
- * registration of event types. The value of the map is the
1848
- * internally created DOM event handler function that handles the
1849
- * DOM events. See addEvent().
1850
- *
1851
- */
1852
- this.eventHandlers = {};
1853
- this.browserEventTypeToExtraEventTypes = {};
1854
- /**
1855
- * The dispatcher function. Events are passed to this function for
1856
- * handling once it was set using the registerDispatcher() method. This is
1857
- * done because the function is passed from another jsbinary, so passing the
1858
- * instance and invoking the method here would require to leave the method
1859
- * unobfuscated.
1860
- */
1861
- this.dispatcher = null;
1862
- /**
1863
- * The list of suspended `EventInfo` that will be dispatched
1864
- * as soon as the `Dispatcher` is registered.
1865
- */
1866
- this.queuedEventInfos = [];
1867
- /** Whether a11y click support has been loaded or not. */
1868
- this.hasA11yClickSupport = false;
1869
- /** Whether to add an a11y click listener. */
1870
- this.addA11yClickListener = false;
1776
+ /** If no event type is defined, defaults to `click`. */
1777
+ const DEFAULT_EVENT_TYPE = EventType.CLICK;
1778
+ /** Resolves actions for Events. */
1779
+ class ActionResolver {
1780
+ constructor({ customEventSupport = false, syntheticMouseEventSupport = false, } = {}) {
1781
+ this.a11yClickSupport = false;
1871
1782
  this.updateEventInfoForA11yClick = undefined;
1872
1783
  this.preventDefaultForA11yClick = undefined;
1873
1784
  this.populateClickOnlyAction = undefined;
1874
- this.containerManager = containerManager;
1875
- if (EventContract.CUSTOM_EVENT_SUPPORT) {
1876
- this.addEvent(EventType.CUSTOM);
1877
- }
1878
- if (EventContract.A11Y_CLICK_SUPPORT) {
1879
- // Add a11y click support to the `EventContract`.
1880
- this.addA11yClickSupport();
1881
- }
1882
- }
1883
- handleEvent(eventType, event, container) {
1884
- const eventInfo = createEventInfoFromParameters(
1885
- /* eventType= */ eventType,
1886
- /* event= */ event,
1887
- /* targetElement= */ event.target,
1888
- /* container= */ container,
1889
- /* timestamp= */ Date.now());
1890
- this.handleEventInfo(eventInfo);
1891
- }
1892
- /**
1893
- * Handle an `EventInfo`.
1894
- */
1895
- handleEventInfo(eventInfo) {
1896
- if (!this.dispatcher) {
1897
- // All events are queued when the dispatcher isn't yet loaded.
1898
- setIsReplay(eventInfo, true);
1899
- this.queuedEventInfos?.push(eventInfo);
1900
- }
1901
- if (EventContract.CUSTOM_EVENT_SUPPORT &&
1902
- getEventType(eventInfo) === EventType.CUSTOM) {
1903
- const detail = getEvent(eventInfo).detail;
1904
- // For custom events, use a secondary dispatch based on the internal
1905
- // custom type of the event.
1906
- if (!detail || !detail['_type']) {
1907
- // This should never happen.
1908
- return;
1785
+ this.customEventSupport = customEventSupport;
1786
+ this.syntheticMouseEventSupport = syntheticMouseEventSupport;
1787
+ }
1788
+ resolve(eventInfo) {
1789
+ if (this.customEventSupport) {
1790
+ if (getEventType(eventInfo) === EventType.CUSTOM) {
1791
+ const detail = getEvent(eventInfo).detail;
1792
+ // For custom events, use a secondary dispatch based on the internal
1793
+ // custom type of the event.
1794
+ if (!detail || !detail['_type']) {
1795
+ // This should never happen.
1796
+ return;
1797
+ }
1798
+ setEventType(eventInfo, detail['_type']);
1909
1799
  }
1910
- setEventType(eventInfo, detail['_type']);
1911
1800
  }
1912
1801
  this.populateAction(eventInfo);
1913
- if (!this.dispatcher) {
1914
- return;
1915
- }
1916
- const globalEventInfo = cloneEventInfo(eventInfo);
1917
- // In some cases, `populateAction` will rewrite `click` events to
1918
- // `clickonly`. Revert back to a regular click, otherwise we won't be able
1919
- // to execute global event handlers registered on click events.
1920
- if (getEventType(globalEventInfo) === EventType.CLICKONLY) {
1921
- setEventType(globalEventInfo, EventType.CLICK);
1922
- }
1923
- this.dispatcher(globalEventInfo, /* dispatch global event */ true);
1924
- const action = getAction(eventInfo);
1925
- if (!action) {
1926
- return;
1927
- }
1928
- if (shouldPreventDefaultBeforeDispatching(getActionElement(action), eventInfo)) {
1929
- preventDefault(getEvent(eventInfo));
1930
- }
1931
- this.dispatcher(eventInfo);
1932
1802
  }
1933
1803
  /**
1934
1804
  * Searches for a jsaction that the DOM event maps to and creates an
@@ -1980,7 +1850,7 @@ class EventContract {
1980
1850
  isModifiedClickEvent(getEvent(eventInfo))) {
1981
1851
  setEventType(eventInfo, EventType.CLICKMOD);
1982
1852
  }
1983
- else if (this.hasA11yClickSupport) {
1853
+ else if (this.a11yClickSupport) {
1984
1854
  this.updateEventInfoForA11yClick(eventInfo);
1985
1855
  }
1986
1856
  // Walk to the parent node, unless the node has a different owner in
@@ -1988,7 +1858,9 @@ class EventContract {
1988
1858
  // shadow root if needed.
1989
1859
  let actionElement = getTargetElement(eventInfo);
1990
1860
  while (actionElement && actionElement !== getContainer(eventInfo)) {
1991
- this.populateActionOnElement(actionElement, eventInfo);
1861
+ if (actionElement.nodeType === Node.ELEMENT_NODE) {
1862
+ this.populateActionOnElement(actionElement, eventInfo);
1863
+ }
1992
1864
  if (getAction(eventInfo)) {
1993
1865
  // An event is handled by at most one jsaction. Thus we stop at the
1994
1866
  // first matching jsaction specified in a jsaction attribute up the
@@ -2011,35 +1883,36 @@ class EventContract {
2011
1883
  // No action found.
2012
1884
  return;
2013
1885
  }
2014
- if (this.hasA11yClickSupport) {
1886
+ if (this.a11yClickSupport) {
2015
1887
  this.preventDefaultForA11yClick(eventInfo);
2016
1888
  }
2017
1889
  // We attempt to handle the mouseenter/mouseleave events here by
2018
1890
  // detecting whether the mouseover/mouseout events correspond to
2019
1891
  // entering/leaving an element.
2020
- if (EventContract.MOUSE_SPECIAL_SUPPORT &&
2021
- (getEventType(eventInfo) === EventType.MOUSEENTER ||
1892
+ if (this.syntheticMouseEventSupport) {
1893
+ if (getEventType(eventInfo) === EventType.MOUSEENTER ||
2022
1894
  getEventType(eventInfo) === EventType.MOUSELEAVE ||
2023
1895
  getEventType(eventInfo) === EventType.POINTERENTER ||
2024
- getEventType(eventInfo) === EventType.POINTERLEAVE)) {
2025
- // We attempt to handle the mouseenter/mouseleave events here by
2026
- // detecting whether the mouseover/mouseout events correspond to
2027
- // entering/leaving an element.
2028
- if (isMouseSpecialEvent(getEvent(eventInfo), getEventType(eventInfo), getActionElement(action))) {
2029
- // If both mouseover/mouseout and mouseenter/mouseleave events are
2030
- // enabled, two separate handlers for mouseover/mouseout are
2031
- // registered. Both handlers will see the same event instance
2032
- // so we create a copy to avoid interfering with the dispatching of
2033
- // the mouseover/mouseout event.
2034
- const copiedEvent = createMouseSpecialEvent(getEvent(eventInfo), getActionElement(action));
2035
- setEvent(eventInfo, copiedEvent);
2036
- // Since the mouseenter/mouseleave events do not bubble, the target
2037
- // of the event is technically the `actionElement` (the node with the
2038
- // `jsaction` attribute)
2039
- setTargetElement(eventInfo, getActionElement(action));
2040
- }
2041
- else {
2042
- unsetAction(eventInfo);
1896
+ getEventType(eventInfo) === EventType.POINTERLEAVE) {
1897
+ // We attempt to handle the mouseenter/mouseleave events here by
1898
+ // detecting whether the mouseover/mouseout events correspond to
1899
+ // entering/leaving an element.
1900
+ if (isMouseSpecialEvent(getEvent(eventInfo), getEventType(eventInfo), getActionElement(action))) {
1901
+ // If both mouseover/mouseout and mouseenter/mouseleave events are
1902
+ // enabled, two separate handlers for mouseover/mouseout are
1903
+ // registered. Both handlers will see the same event instance
1904
+ // so we create a copy to avoid interfering with the dispatching of
1905
+ // the mouseover/mouseout event.
1906
+ const copiedEvent = createMouseSpecialEvent(getEvent(eventInfo), getActionElement(action));
1907
+ setEvent(eventInfo, copiedEvent);
1908
+ // Since the mouseenter/mouseleave events do not bubble, the target
1909
+ // of the event is technically the `actionElement` (the node with the
1910
+ // `jsaction` attribute)
1911
+ setTargetElement(eventInfo, getActionElement(action));
1912
+ }
1913
+ else {
1914
+ unsetAction(eventInfo);
1915
+ }
2043
1916
  }
2044
1917
  }
2045
1918
  }
@@ -2048,25 +1921,192 @@ class EventContract {
2048
1921
  * action the given event is mapped to, if any. It parses the
2049
1922
  * attribute value and stores it in a property on the node for
2050
1923
  * subsequent retrieval without re-parsing and re-accessing the
2051
- * attribute. In order to fully qualify jsaction names using a
2052
- * namespace, the DOM is searched starting at the current node and
2053
- * going through ancestor nodes until a jsnamespace attribute is
2054
- * found.
1924
+ * attribute.
2055
1925
  *
2056
1926
  * @param actionElement The DOM node to retrieve the jsaction map from.
2057
1927
  * @param eventInfo `EventInfo` to set `action` and `actionElement` if an
2058
1928
  * action is found on the `actionElement`.
2059
1929
  */
2060
1930
  populateActionOnElement(actionElement, eventInfo) {
2061
- const actionMap = parseActions(actionElement, getContainer(eventInfo));
1931
+ const actionMap = this.parseActions(actionElement);
2062
1932
  const actionName = actionMap[getEventType(eventInfo)];
2063
1933
  if (actionName !== undefined) {
2064
1934
  setAction(eventInfo, actionName, actionElement);
2065
1935
  }
2066
- if (this.hasA11yClickSupport) {
1936
+ if (this.a11yClickSupport) {
2067
1937
  this.populateClickOnlyAction(actionElement, eventInfo, actionMap);
2068
1938
  }
2069
1939
  }
1940
+ /**
1941
+ * Parses and caches an element's jsaction element into a map.
1942
+ *
1943
+ * This is primarily for internal use.
1944
+ *
1945
+ * @param actionElement The DOM node to retrieve the jsaction map from.
1946
+ * @return Map from event to qualified name of the jsaction bound to it.
1947
+ */
1948
+ parseActions(actionElement) {
1949
+ let actionMap = get(actionElement);
1950
+ if (!actionMap) {
1951
+ const jsactionAttribute = actionElement.getAttribute(Attribute.JSACTION);
1952
+ if (!jsactionAttribute) {
1953
+ actionMap = EMPTY_ACTION_MAP;
1954
+ set(actionElement, actionMap);
1955
+ }
1956
+ else {
1957
+ actionMap = getParsed(jsactionAttribute);
1958
+ if (!actionMap) {
1959
+ actionMap = {};
1960
+ const values = jsactionAttribute.split(REGEXP_SEMICOLON);
1961
+ for (let idx = 0; idx < values.length; idx++) {
1962
+ const value = values[idx];
1963
+ if (!value) {
1964
+ continue;
1965
+ }
1966
+ const colon = value.indexOf(Char.EVENT_ACTION_SEPARATOR);
1967
+ const hasColon = colon !== -1;
1968
+ const type = hasColon ? value.substr(0, colon).trim() : DEFAULT_EVENT_TYPE;
1969
+ const action = hasColon ? value.substr(colon + 1).trim() : value;
1970
+ actionMap[type] = action;
1971
+ }
1972
+ setParsed(jsactionAttribute, actionMap);
1973
+ }
1974
+ set(actionElement, actionMap);
1975
+ }
1976
+ }
1977
+ return actionMap;
1978
+ }
1979
+ addA11yClickSupport(updateEventInfoForA11yClick, preventDefaultForA11yClick, populateClickOnlyAction) {
1980
+ this.a11yClickSupport = true;
1981
+ this.updateEventInfoForA11yClick = updateEventInfoForA11yClick;
1982
+ this.preventDefaultForA11yClick = preventDefaultForA11yClick;
1983
+ this.populateClickOnlyAction = populateClickOnlyAction;
1984
+ }
1985
+ }
1986
+
1987
+ /**
1988
+ * @define Support for accessible click actions. This flag can be overridden in
1989
+ * a build rule.
1990
+ */
1991
+ const A11Y_CLICK_SUPPORT = false;
1992
+ /**
1993
+ * @define Support for the non-bubbling mouseenter and mouseleave events. This
1994
+ * flag can be overridden in a build rule.
1995
+ */
1996
+ const MOUSE_SPECIAL_SUPPORT = false;
1997
+ /**
1998
+ * @define Support for custom events, which are type EventType.CUSTOM. These are
1999
+ * native DOM events with an additional type field and an optional payload.
2000
+ */
2001
+ const CUSTOM_EVENT_SUPPORT = false;
2002
+
2003
+ /**
2004
+ * @fileoverview Implements the local event handling contract. This
2005
+ * allows DOM objects in a container that enters into this contract to
2006
+ * define event handlers which are executed in a local context.
2007
+ *
2008
+ * One EventContract instance can manage the contract for multiple
2009
+ * containers, which are added using the addContainer() method.
2010
+ *
2011
+ * Events can be registered using the addEvent() method.
2012
+ *
2013
+ * A Dispatcher is added using the registerDispatcher() method. Until there is
2014
+ * a dispatcher, events are queued. The idea is that the EventContract
2015
+ * class is inlined in the HTML of the top level page and instantiated
2016
+ * right after the start of <body>. The Dispatcher class is contained
2017
+ * in the external deferred js, and instantiated and registered with
2018
+ * EventContract when the external javascript in the page loads. The
2019
+ * external javascript will also register the jsaction handlers, which
2020
+ * then pick up the queued events at the time of registration.
2021
+ *
2022
+ * Since this class is meant to be inlined in the main page HTML, the
2023
+ * size of the binary compiled from this file MUST be kept as small as
2024
+ * possible and thus its dependencies to a minimum.
2025
+ */
2026
+ /**
2027
+ * EventContract intercepts events in the bubbling phase at the
2028
+ * boundary of a container element, and maps them to generic actions
2029
+ * which are specified using the custom jsaction attribute in
2030
+ * HTML. Behavior of the application is then specified in terms of
2031
+ * handler for such actions, cf. jsaction.Dispatcher in dispatcher.js.
2032
+ *
2033
+ * This has several benefits: (1) No DOM event handlers need to be
2034
+ * registered on the specific elements in the UI. (2) The set of
2035
+ * events that the application has to handle can be specified in terms
2036
+ * of the semantics of the application, rather than in terms of DOM
2037
+ * events. (3) Invocation of handlers can be delayed and handlers can
2038
+ * be delay loaded in a generic way.
2039
+ */
2040
+ class EventContract {
2041
+ static { this.CUSTOM_EVENT_SUPPORT = CUSTOM_EVENT_SUPPORT; }
2042
+ static { this.A11Y_CLICK_SUPPORT = A11Y_CLICK_SUPPORT; }
2043
+ static { this.MOUSE_SPECIAL_SUPPORT = MOUSE_SPECIAL_SUPPORT; }
2044
+ constructor(containerManager) {
2045
+ this.actionResolver = new ActionResolver({
2046
+ customEventSupport: EventContract.CUSTOM_EVENT_SUPPORT,
2047
+ syntheticMouseEventSupport: EventContract.MOUSE_SPECIAL_SUPPORT,
2048
+ });
2049
+ /**
2050
+ * The DOM events which this contract covers. Used to prevent double
2051
+ * registration of event types. The value of the map is the
2052
+ * internally created DOM event handler function that handles the
2053
+ * DOM events. See addEvent().
2054
+ *
2055
+ */
2056
+ this.eventHandlers = {};
2057
+ this.browserEventTypeToExtraEventTypes = {};
2058
+ /**
2059
+ * The dispatcher function. Events are passed to this function for
2060
+ * handling once it was set using the registerDispatcher() method. This is
2061
+ * done because the function is passed from another jsbinary, so passing the
2062
+ * instance and invoking the method here would require to leave the method
2063
+ * unobfuscated.
2064
+ */
2065
+ this.dispatcher = null;
2066
+ /**
2067
+ * The list of suspended `EventInfo` that will be dispatched
2068
+ * as soon as the `Dispatcher` is registered.
2069
+ */
2070
+ this.queuedEventInfos = [];
2071
+ /** Whether to add an a11y click listener. */
2072
+ this.addA11yClickListener = false;
2073
+ this.containerManager = containerManager;
2074
+ if (EventContract.CUSTOM_EVENT_SUPPORT) {
2075
+ this.addEvent(EventType.CUSTOM);
2076
+ }
2077
+ if (EventContract.A11Y_CLICK_SUPPORT) {
2078
+ // Add a11y click support to the `EventContract`.
2079
+ this.addA11yClickSupport();
2080
+ }
2081
+ }
2082
+ handleEvent(eventType, event, container) {
2083
+ const eventInfo = createEventInfoFromParameters(
2084
+ /* eventType= */ eventType,
2085
+ /* event= */ event,
2086
+ /* targetElement= */ event.target,
2087
+ /* container= */ container,
2088
+ /* timestamp= */ Date.now());
2089
+ this.handleEventInfo(eventInfo);
2090
+ }
2091
+ /**
2092
+ * Handle an `EventInfo`.
2093
+ */
2094
+ handleEventInfo(eventInfo) {
2095
+ if (!this.dispatcher) {
2096
+ // All events are queued when the dispatcher isn't yet loaded.
2097
+ setIsReplay(eventInfo, true);
2098
+ this.queuedEventInfos?.push(eventInfo);
2099
+ return;
2100
+ }
2101
+ this.actionResolver.resolve(eventInfo);
2102
+ const action = getAction(eventInfo);
2103
+ if (action) {
2104
+ if (shouldPreventDefaultBeforeDispatching(getActionElement(action), eventInfo)) {
2105
+ preventDefault(getEvent(eventInfo));
2106
+ }
2107
+ }
2108
+ this.dispatcher(eventInfo);
2109
+ }
2070
2110
  /**
2071
2111
  * Enables jsaction handlers to be called for the event type given by
2072
2112
  * name.
@@ -2118,10 +2158,10 @@ class EventContract {
2118
2158
  * in the provided event contract. Once all the events are replayed, it cleans
2119
2159
  * up the early contract.
2120
2160
  */
2121
- replayEarlyEvents() {
2161
+ replayEarlyEvents(earlyJsactionContainer = window) {
2122
2162
  // Check if the early contract is present and prevent calling this function
2123
2163
  // more than once.
2124
- const earlyJsactionData = window._ejsa;
2164
+ const earlyJsactionData = earlyJsactionContainer._ejsa;
2125
2165
  if (!earlyJsactionData) {
2126
2166
  return;
2127
2167
  }
@@ -2139,13 +2179,10 @@ class EventContract {
2139
2179
  }
2140
2180
  }
2141
2181
  // Clean up the early contract.
2142
- const earlyEventTypes = earlyJsactionData.et;
2143
2182
  const earlyEventHandler = earlyJsactionData.h;
2144
- for (let idx = 0; idx < earlyEventTypes.length; idx++) {
2145
- const eventType = earlyEventTypes[idx];
2146
- window.document.documentElement.removeEventListener(eventType, earlyEventHandler);
2147
- }
2148
- delete window._ejsa;
2183
+ removeEventListeners(earlyJsactionData.c, earlyJsactionData.et, earlyEventHandler);
2184
+ removeEventListeners(earlyJsactionData.c, earlyJsactionData.etc, earlyEventHandler, true);
2185
+ delete earlyJsactionContainer._ejsa;
2149
2186
  }
2150
2187
  /**
2151
2188
  * Returns all JSAction event types that have been registered for a given
@@ -2226,10 +2263,12 @@ class EventContract {
2226
2263
  */
2227
2264
  addA11yClickSupportImpl(updateEventInfoForA11yClick, preventDefaultForA11yClick, populateClickOnlyAction) {
2228
2265
  this.addA11yClickListener = true;
2229
- this.hasA11yClickSupport = true;
2230
- this.updateEventInfoForA11yClick = updateEventInfoForA11yClick;
2231
- this.preventDefaultForA11yClick = preventDefaultForA11yClick;
2232
- this.populateClickOnlyAction = populateClickOnlyAction;
2266
+ this.actionResolver.addA11yClickSupport(updateEventInfoForA11yClick, preventDefaultForA11yClick, populateClickOnlyAction);
2267
+ }
2268
+ }
2269
+ function removeEventListeners(container, eventTypes, earlyEventHandler, capture) {
2270
+ for (let idx = 0; idx < eventTypes.length; idx++) {
2271
+ container.removeEventListener(eventTypes[idx], earlyEventHandler, /* useCapture */ capture);
2233
2272
  }
2234
2273
  }
2235
2274
  /**
@@ -2254,174 +2293,81 @@ function shouldPreventDefaultBeforeDispatching(actionElement, eventInfo) {
2254
2293
  (getEventType(eventInfo) === EventType.CLICK ||
2255
2294
  getEventType(eventInfo) === EventType.CLICKMOD));
2256
2295
  }
2296
+
2257
2297
  /**
2258
- * Parses and caches an element's jsaction element into a map.
2259
- *
2260
- * This is primarily for internal use.
2261
- *
2262
- * @param actionElement The DOM node to retrieve the jsaction map from.
2263
- * @param container The node which limits the namespace lookup for a jsaction
2264
- * name. The container node itself will not be searched.
2265
- * @return Map from event to qualified name of the jsaction bound to it.
2266
- */
2267
- function parseActions(actionElement, container) {
2268
- let actionMap = get(actionElement);
2269
- if (!actionMap) {
2270
- const jsactionAttribute = getAttr(actionElement, Attribute.JSACTION);
2271
- if (!jsactionAttribute) {
2272
- actionMap = EMPTY_ACTION_MAP;
2273
- set(actionElement, actionMap);
2274
- }
2275
- else {
2276
- actionMap = getParsed(jsactionAttribute);
2277
- if (!actionMap) {
2278
- actionMap = {};
2279
- const values = jsactionAttribute.split(REGEXP_SEMICOLON);
2280
- for (let idx = 0; idx < values.length; idx++) {
2281
- const value = values[idx];
2282
- if (!value) {
2283
- continue;
2284
- }
2285
- const colon = value.indexOf(Char.EVENT_ACTION_SEPARATOR);
2286
- const hasColon = colon !== -1;
2287
- const type = hasColon ? stringTrim(value.substr(0, colon)) : DEFAULT_EVENT_TYPE;
2288
- const action = hasColon ? stringTrim(value.substr(colon + 1)) : value;
2289
- actionMap[type] = action;
2290
- }
2291
- setParsed(jsactionAttribute, actionMap);
2292
- }
2293
- // If namespace support is active we need to augment the (potentially
2294
- // cached) jsaction mapping with the namespace.
2295
- if (EventContract.JSNAMESPACE_SUPPORT) {
2296
- const noNs = actionMap;
2297
- actionMap = {};
2298
- for (const type in noNs) {
2299
- actionMap[type] = getFullyQualifiedAction(noNs[type], actionElement, container);
2300
- }
2301
- }
2302
- set(actionElement, actionMap);
2303
- }
2298
+ * EarlyEventContract intercepts events in the bubbling phase at the
2299
+ * boundary of the document body. This mapping will be passed to the
2300
+ * late-loaded EventContract.
2301
+ */
2302
+ class EarlyEventContract {
2303
+ constructor(replaySink = window, container = window.document.documentElement) {
2304
+ this.replaySink = replaySink;
2305
+ this.container = container;
2306
+ this.replaySink._ejsa = {
2307
+ c: container,
2308
+ q: [],
2309
+ et: [],
2310
+ etc: [],
2311
+ h: (event) => {
2312
+ const eventInfo = createEventInfoFromParameters(event.type, event, event.target, window.document.documentElement, Date.now());
2313
+ this.replaySink._ejsa.q.push(eventInfo);
2314
+ },
2315
+ };
2304
2316
  }
2305
- return actionMap;
2306
- }
2307
- /**
2308
- * Returns the fully qualified jsaction action. If the given jsaction
2309
- * name doesn't already contain the namespace, the function iterates
2310
- * over ancestor nodes until a jsnamespace attribute is found, and
2311
- * uses the value of that attribute as the namespace.
2312
- *
2313
- * @param action The jsaction action to resolve.
2314
- * @param start The node from which to start searching for a jsnamespace
2315
- * attribute.
2316
- * @param container The node which limits the search for a jsnamespace
2317
- * attribute. This node will be searched.
2318
- * @return The fully qualified name of the jsaction. If no namespace is found,
2319
- * returns the unqualified name in case it exists in the global namespace.
2320
- */
2321
- function getFullyQualifiedAction(action, start, container) {
2322
- if (EventContract.JSNAMESPACE_SUPPORT) {
2323
- if (isNamespacedAction(action)) {
2324
- return action;
2325
- }
2326
- let node = start;
2327
- while (node) {
2328
- const namespace = getNamespaceFromElement(node);
2329
- if (namespace) {
2330
- return namespace + Char.NAMESPACE_ACTION_SEPARATOR + action;
2331
- }
2332
- // If this node is the container, stop.
2333
- if (node === container) {
2334
- break;
2335
- }
2336
- node = node.parentNode;
2317
+ /**
2318
+ * Installs a list of event types for container .
2319
+ */
2320
+ addEvents(types, capture) {
2321
+ const replaySink = this.replaySink._ejsa;
2322
+ for (let idx = 0; idx < types.length; idx++) {
2323
+ const eventType = types[idx];
2324
+ const eventTypes = capture ? replaySink.etc : replaySink.et;
2325
+ eventTypes.push(eventType);
2326
+ this.container.addEventListener(eventType, replaySink.h, capture);
2337
2327
  }
2338
2328
  }
2339
- return action;
2340
- }
2341
- /**
2342
- * Checks if a jsaction action contains a namespace part.
2343
- */
2344
- function isNamespacedAction(action) {
2345
- return action.indexOf(Char.NAMESPACE_ACTION_SEPARATOR) >= 0;
2346
- }
2347
- /**
2348
- * Returns the value of the jsnamespace attribute of the given node.
2349
- * Also caches the value for subsequent lookups.
2350
- * @param element The node whose jsnamespace attribute is being asked for.
2351
- * @return The value of the jsnamespace attribute, or null if not found.
2352
- */
2353
- function getNamespaceFromElement(element) {
2354
- let namespace = getNamespace(element);
2355
- // Only query for the attribute if it has not been queried for
2356
- // before. getAttr() returns null if an attribute is not present. Thus,
2357
- // namespace is string|null if the query took place in the past, or
2358
- // undefined if the query did not take place.
2359
- if (namespace === undefined) {
2360
- namespace = getAttr(element, Attribute.JSNAMESPACE);
2361
- setNamespace(element, namespace);
2362
- }
2363
- return namespace;
2364
- }
2365
- /**
2366
- * Accesses the event handler attribute value of a DOM node. It guards
2367
- * against weird situations (described in the body) that occur in
2368
- * connection with nodes that are removed from their document.
2369
- * @param element The DOM element.
2370
- * @param attribute The name of the attribute to access.
2371
- * @return The attribute value if it was found, null otherwise.
2372
- */
2373
- function getAttr(element, attribute) {
2374
- let value = null;
2375
- // NOTE: Nodes in IE do not always have a getAttribute
2376
- // method defined. This is the case where sourceElement has in
2377
- // fact been removed from the DOM before eventContract begins
2378
- // handling - where a parentNode does not have getAttribute
2379
- // defined.
2380
- // NOTE: We must use the 'in' operator instead of the regular dot
2381
- // notation, since the latter fails in IE8 if the getAttribute method is not
2382
- // defined. See b/7139109.
2383
- if ('getAttribute' in element) {
2384
- value = element.getAttribute(attribute);
2385
- }
2386
- return value;
2387
- }
2388
- /**
2389
- * Helper function to trim whitespace from the beginning and the end
2390
- * of the string. This deliberately doesn't use the closure equivalent
2391
- * to keep dependencies small.
2392
- * @param str Input string.
2393
- * @return Trimmed string.
2394
- */
2395
- function stringTrim(str) {
2396
- if (typeof String.prototype.trim === 'function') {
2397
- return str.trim();
2398
- }
2399
- const trimmedLeft = str.replace(/^\s+/, '');
2400
- return trimmedLeft.replace(/\s+$/, '');
2401
2329
  }
2402
2330
 
2403
2331
  /**
2404
2332
  * Provides a factory function for bootstrapping an event contract on a
2405
- * window object.
2406
- * @param field The property on the window that the event contract will be placed on.
2333
+ * specified object (by default, exposed on the `window`).
2334
+ * @param field The property on the object that the event contract will be placed on.
2407
2335
  * @param container The container that listens to events
2408
2336
  * @param appId A given identifier for an application. If there are multiple apps on the page
2409
2337
  * then this is how contracts can be initialized for each one.
2410
2338
  * @param events An array of event names that should be listened to.
2411
- * @param anyWindow The global window object that should receive the event contract.
2412
- * @returns The `event` contract. This is both assigned to `anyWindow` and returned for testing.
2339
+ * @param earlyJsactionTracker The object that should receive the event contract.
2413
2340
  */
2414
- function bootstrapEventContract(field, container, appId, events, anyWindow = window) {
2415
- if (!anyWindow[field]) {
2416
- anyWindow[field] = {};
2341
+ function bootstrapEventContract(field, container, appId, events, earlyJsactionTracker = window) {
2342
+ if (!earlyJsactionTracker[field]) {
2343
+ earlyJsactionTracker[field] = {};
2417
2344
  }
2418
2345
  const eventContract = new EventContract(new EventContractContainer(container));
2419
- anyWindow[field][appId] = eventContract;
2346
+ earlyJsactionTracker[field][appId] = eventContract;
2420
2347
  for (const ev of events) {
2421
2348
  eventContract.addEvent(ev);
2422
2349
  }
2423
- return eventContract;
2350
+ }
2351
+ /**
2352
+ * Provides a factory function for bootstrapping an event contract on a
2353
+ * specified object (by default, exposed on the `window`).
2354
+ * @param field The property on the object that the event contract will be placed on.
2355
+ * @param container The container that listens to events
2356
+ * @param appId A given identifier for an application. If there are multiple apps on the page
2357
+ * then this is how contracts can be initialized for each one.
2358
+ * @param eventTypes An array of event names that should be listened to.
2359
+ * @param captureEventTypes An array of event names that should be listened to with capture.
2360
+ * @param earlyJsactionTracker The object that should receive the event contract.
2361
+ */
2362
+ function bootstrapEarlyEventContract(field, container, appId, eventTypes, captureEventTypes, earlyJsactionTracker = window) {
2363
+ if (!earlyJsactionTracker[field]) {
2364
+ earlyJsactionTracker[field] = {};
2365
+ }
2366
+ earlyJsactionTracker[field][appId] = {};
2367
+ const eventContract = new EarlyEventContract(earlyJsactionTracker[field][appId], container);
2368
+ eventContract.addEvents(eventTypes);
2369
+ eventContract.addEvents(captureEventTypes, true);
2424
2370
  }
2425
2371
 
2426
- export { Dispatcher, EventContract, EventContractContainer, EventInfoWrapper, bootstrapEventContract, registerDispatcher };
2372
+ export { Dispatcher, EventContract, EventContractContainer, EventInfoWrapper, bootstrapEarlyEventContract, bootstrapEventContract, registerDispatcher };
2427
2373
  //# sourceMappingURL=event-dispatch.mjs.map