propagating.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.propagating = factory());
  5. }(this, (function () { 'use strict';
  6. var _firstTarget = null; // singleton, will contain the target element where the touch event started
  7. /**
  8. * Extend an Hammer.js instance with event propagation.
  9. *
  10. * Features:
  11. * - Events emitted by hammer will propagate in order from child to parent
  12. * elements.
  13. * - Events are extended with a function `event.stopPropagation()` to stop
  14. * propagation to parent elements.
  15. * - An option `preventDefault` to stop all default browser behavior.
  16. *
  17. * Usage:
  18. * var hammer = propagatingHammer(new Hammer(element));
  19. * var hammer = propagatingHammer(new Hammer(element), {preventDefault: true});
  20. *
  21. * @param {Hammer.Manager} hammer An hammer instance.
  22. * @param {Object} [options] Available options:
  23. * - `preventDefault: true | false | 'mouse' | 'touch' | 'pen'`.
  24. * Enforce preventing the default browser behavior.
  25. * Cannot be set to `false`.
  26. * @return {Hammer.Manager} Returns the same hammer instance with extended
  27. * functionality
  28. */
  29. function propagating(hammer, options) {
  30. var _options = options || {
  31. preventDefault: false
  32. };
  33. if (hammer.Manager) {
  34. // This looks like the Hammer constructor.
  35. // Overload the constructors with our own.
  36. var Hammer = hammer;
  37. var PropagatingHammer = function(element, options) {
  38. var o = Object.create(_options);
  39. if (options) Hammer.assign(o, options);
  40. return propagating(new Hammer(element, o), o);
  41. };
  42. Hammer.assign(PropagatingHammer, Hammer);
  43. PropagatingHammer.Manager = function (element, options) {
  44. var o = Object.create(_options);
  45. if (options) Hammer.assign(o, options);
  46. return propagating(new Hammer.Manager(element, o), o);
  47. };
  48. return PropagatingHammer;
  49. }
  50. // create a wrapper object which will override the functions
  51. // `on`, `off`, `destroy`, and `emit` of the hammer instance
  52. var wrapper = Object.create(hammer);
  53. // attach to DOM element
  54. var element = hammer.element;
  55. if(!element.hammer) element.hammer = [];
  56. element.hammer.push(wrapper);
  57. // register an event to catch the start of a gesture and store the
  58. // target in a singleton
  59. hammer.on('hammer.input', function (event) {
  60. if (_options.preventDefault === true || (_options.preventDefault === event.pointerType)) {
  61. event.preventDefault();
  62. }
  63. if (event.isFirst) {
  64. _firstTarget = event.target;
  65. }
  66. });
  67. /** @type {Object.<String, Array.<function>>} */
  68. wrapper._handlers = {};
  69. /**
  70. * Register a handler for one or multiple events
  71. * @param {String} events A space separated string with events
  72. * @param {function} handler A callback function, called as handler(event)
  73. * @returns {Hammer.Manager} Returns the hammer instance
  74. */
  75. wrapper.on = function (events, handler) {
  76. // register the handler
  77. split(events).forEach(function (event) {
  78. var _handlers = wrapper._handlers[event];
  79. if (!_handlers) {
  80. wrapper._handlers[event] = _handlers = [];
  81. // register the static, propagated handler
  82. hammer.on(event, propagatedHandler);
  83. }
  84. _handlers.push(handler);
  85. });
  86. return wrapper;
  87. };
  88. /**
  89. * Unregister a handler for one or multiple events
  90. * @param {String} events A space separated string with events
  91. * @param {function} [handler] Optional. The registered handler. If not
  92. * provided, all handlers for given events
  93. * are removed.
  94. * @returns {Hammer.Manager} Returns the hammer instance
  95. */
  96. wrapper.off = function (events, handler) {
  97. // unregister the handler
  98. split(events).forEach(function (event) {
  99. var _handlers = wrapper._handlers[event];
  100. if (_handlers) {
  101. _handlers = handler ? _handlers.filter(function (h) {
  102. return h !== handler;
  103. }) : [];
  104. if (_handlers.length > 0) {
  105. wrapper._handlers[event] = _handlers;
  106. }
  107. else {
  108. // remove static, propagated handler
  109. hammer.off(event, propagatedHandler);
  110. delete wrapper._handlers[event];
  111. }
  112. }
  113. });
  114. return wrapper;
  115. };
  116. /**
  117. * Emit to the event listeners
  118. * @param {string} eventType
  119. * @param {Event} event
  120. */
  121. wrapper.emit = function(eventType, event) {
  122. _firstTarget = event.target;
  123. hammer.emit(eventType, event);
  124. };
  125. wrapper.destroy = function () {
  126. // Detach from DOM element
  127. var hammers = hammer.element.hammer;
  128. var idx = hammers.indexOf(wrapper);
  129. if(idx !== -1) hammers.splice(idx,1);
  130. if(!hammers.length) delete hammer.element.hammer;
  131. // clear all handlers
  132. wrapper._handlers = {};
  133. // call original hammer destroy
  134. hammer.destroy();
  135. };
  136. // split a string with space separated words
  137. function split(events) {
  138. return events.match(/[^ ]+/g);
  139. }
  140. /**
  141. * A static event handler, applying event propagation.
  142. * @param {Object} event
  143. */
  144. function propagatedHandler(event) {
  145. // let only a single hammer instance handle this event
  146. if (event.type !== 'hammer.input') {
  147. // it is possible that the same srcEvent is used with multiple hammer events,
  148. // we keep track on which events are handled in an object _handled
  149. if (!event.srcEvent._handled) {
  150. event.srcEvent._handled = {};
  151. }
  152. if (event.srcEvent._handled[event.type]) {
  153. return;
  154. }
  155. else {
  156. event.srcEvent._handled[event.type] = true;
  157. }
  158. }
  159. // attach a stopPropagation function to the event
  160. var stopped = false;
  161. event.stopPropagation = function () {
  162. stopped = true;
  163. };
  164. //wrap the srcEvent's stopPropagation to also stop hammer propagation:
  165. var srcStop = event.srcEvent.stopPropagation.bind(event.srcEvent);
  166. if(typeof srcStop == "function") {
  167. event.srcEvent.stopPropagation = function(){
  168. srcStop();
  169. event.stopPropagation();
  170. };
  171. }
  172. // attach firstTarget property to the event
  173. event.firstTarget = _firstTarget;
  174. // propagate over all elements (until stopped)
  175. var elem = _firstTarget;
  176. while (elem && !stopped) {
  177. var elemHammer = elem.hammer;
  178. if(elemHammer){
  179. var _handlers;
  180. for(var k = 0; k < elemHammer.length; k++){
  181. _handlers = elemHammer[k]._handlers[event.type];
  182. if(_handlers) for (var i = 0; i < _handlers.length && !stopped; i++) {
  183. _handlers[i](event);
  184. }
  185. }
  186. }
  187. elem = elem.parentNode;
  188. }
  189. }
  190. return wrapper;
  191. }
  192. return propagating;
  193. })));