{"version":3,"file":"event-listener.mjs","sources":["../src/index.ts"],"sourcesContent":["/**\n * Advanced event listener based on subscribe / publish pattern.\n *\n * @see https://www.patterns.dev/posts/classic-design-patterns/#observerpatternjavascript\n * @see https://gist.github.com/shystruk/d16c0ee7ac7d194da9644e5d740c8338#file-subpub-js\n * @see https://hackernoon.com/do-you-still-register-window-event-listeners-in-each-component-react-in-example-31a4b1f6f1c8\n */\nimport { version } from \"../package.json\";\n\nimport {\n  AnimationEvent,\n  AnimationEventHandler,\n  ChangeEvent,\n  ChangeEventHandler,\n  ClipboardEvent,\n  ClipboardEventHandler,\n  CompositionEvent,\n  CompositionEventHandler,\n  DragEvent,\n  DragEventHandler,\n  EventRegistryEntry,\n  EventsRegistry,\n  FocusEvent,\n  FocusEventHandler,\n  FormEvent,\n  FormEventHandler,\n  KeyboardEvent,\n  KeyboardEventHandler,\n  MouseEvent,\n  MouseEventHandler,\n  NativeEvent,\n  NativeEventHandler,\n  PointerEvent,\n  PointerEventHandler,\n  PossibleEventTarget,\n  TouchEvent,\n  TouchEventHandler,\n  TransitionEvent,\n  TransitionEventHandler,\n  UIEvent,\n  UIEventHandler,\n  WheelEvent,\n  WheelEventHandler,\n} from \"./types\";\n\nconst registry: EventsRegistry = {};\n\n/**\n * The global event listener. This function must be a Function.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget\n */\nconst globalListener = (e: NativeEvent) => {\n  const { type, currentTarget } = e;\n\n  registry[type].forEach((listenersMap, element) => {\n    /* istanbul ignore else @preserve */\n    if (currentTarget === element) {\n      listenersMap.forEach((options, listener) => {\n        listener.apply(element, [e]);\n\n        if (typeof options === \"object\" && options.once) {\n          removeListener(element, type, listener, options);\n        }\n      });\n    }\n  });\n};\n\n/**\n * Register a new listener with its options and attach the `globalListener`\n * to the target if this is the first listener.\n */\nconst addListener = <T = Element, L = EventListener>(\n  element: T,\n  eventType: string,\n  listener: L,\n  options?: AddEventListenerOptions,\n): void => {\n  // get element listeners first\n  /* istanbul ignore else @preserve */\n  if (!registry[eventType]) {\n    registry[eventType] = new Map();\n  }\n  const oneEventMap = registry[eventType];\n\n  /* istanbul ignore else @preserve */\n  if (!oneEventMap.has(element as PossibleEventTarget)) {\n    oneEventMap.set(element as PossibleEventTarget, new Map());\n  }\n  const oneElementMap = oneEventMap.get(\n    element as PossibleEventTarget,\n  ) as EventRegistryEntry<T, L>;\n\n  // get listeners size\n  const { size } = oneElementMap;\n\n  // register listener with its options\n  oneElementMap.set(listener, options);\n\n  // add listener last\n  /* istanbul ignore else @preserve */\n  if (!size) {\n    (element as PossibleEventTarget).addEventListener(\n      eventType,\n      globalListener as unknown as EventListener,\n      options,\n    );\n  }\n};\n\n/**\n * Remove a listener from registry and detach the `globalListener`\n * if no listeners are found in the registry.\n */\nconst removeListener = <T = Element, L = EventListener>(\n  element: T,\n  eventType: string,\n  listener: L,\n  options?: AddEventListenerOptions,\n): void => {\n  // get listener first\n  const oneEventMap = registry[eventType];\n  const oneElementMap = oneEventMap &&\n    (oneEventMap.get(element as PossibleEventTarget) as EventRegistryEntry<T>);\n  const savedOptions = oneElementMap &&\n    oneElementMap.get(listener as NativeEventHandler<typeof element>);\n\n  // also recover initial options\n  const eventOptions = savedOptions !== undefined ? savedOptions : options;\n\n  // unsubscribe second, remove from registry\n  /* istanbul ignore else @preserve */\n  if (\n    oneElementMap &&\n    oneElementMap.has(listener as NativeEventHandler<typeof element>)\n  ) {\n    oneElementMap.delete(listener as NativeEventHandler<typeof element>);\n  }\n  /* istanbul ignore else @preserve */\n  if (oneEventMap && (!oneElementMap || !oneElementMap.size)) {\n    oneEventMap.delete(element as PossibleEventTarget);\n  }\n  /* istanbul ignore else @preserve */\n  if (!oneEventMap || !oneEventMap.size) delete registry[eventType];\n\n  // remove listener last\n  /* istanbul ignore else @preserve */\n  if (!oneElementMap || !oneElementMap.size) {\n    (element as PossibleEventTarget).removeEventListener(\n      eventType,\n      globalListener as unknown as EventListener,\n      eventOptions,\n    );\n  }\n};\n\n// alias main methods\nconst on: typeof addListener = addListener;\nconst off: typeof removeListener = removeListener;\n\nexport {\n  addListener,\n  globalListener,\n  off,\n  on,\n  registry,\n  removeListener,\n  version,\n};\nexport type {\n  AnimationEvent,\n  AnimationEventHandler,\n  ChangeEvent,\n  ChangeEventHandler,\n  ClipboardEvent,\n  ClipboardEventHandler,\n  CompositionEvent,\n  CompositionEventHandler,\n  DragEvent,\n  DragEventHandler,\n  EventRegistryEntry,\n  EventsRegistry,\n  FocusEvent,\n  FocusEventHandler,\n  FormEvent,\n  FormEventHandler,\n  KeyboardEvent,\n  KeyboardEventHandler,\n  MouseEvent,\n  MouseEventHandler,\n  NativeEvent,\n  NativeEventHandler,\n  PointerEvent,\n  PointerEventHandler,\n  PossibleEventTarget,\n  TouchEvent,\n  TouchEventHandler,\n  TransitionEvent,\n  TransitionEventHandler,\n  UIEvent,\n  UIEventHandler,\n  WheelEvent,\n  WheelEventHandler,\n};\n"],"names":["registry","globalListener","e","type","currentTarget","listenersMap","element","options","listener","removeListener","addListener","eventType","oneEventMap","oneElementMap","size","savedOptions","eventOptions","on","off"],"mappings":"oBAuCMA,IAA2B,CAAA,GAG3BC,IAAiB,CAACC,MAAmB;AACnC,QAAA,EAAE,MAAAC,GAAM,eAAAC,EAAA,IAAkBF;AAEhC,EAAAF,EAASG,CAAI,EAAE,QAAQ,CAACE,GAAcC,MAAY;AAEhD,IAAIF,MAAkBE,KACPD,EAAA,QAAQ,CAACE,GAASC,MAAa;AAC1C,MAAAA,EAAS,MAAMF,GAAS,CAACJ,CAAC,CAAC,GAEvB,OAAOK,KAAY,YAAYA,EAAQ,QAC1BE,EAAAH,GAASH,GAAMK,GAAUD,CAAO;AAAA,IACjD,CACD;AAAA,EACH,CACD;AACH,GAGMG,IAAc,CAClBJ,GACAK,GACAH,GACAD,MACS;AAGL,EAACP,EAASW,CAAS,MACZX,EAAAW,CAAS,IAAI,oBAAI,IAAI;AAE1B,QAAAC,IAAcZ,EAASW,CAAS;AAGtC,EAAKC,EAAY,IAAIN,CAA8B,KACjDM,EAAY,IAAIN,GAAoC,oBAAA,IAAA,CAAK;AAE3D,QAAMO,IAAgBD,EAAY;AAAA,IAChCN;AAAA,EACF,GAGM,EAAE,MAAAQ,MAASD;AAGH,EAAAA,EAAA,IAAIL,GAAUD,CAAO,GAI9BO,KACFR,EAAgC;AAAA,IAC/BK;AAAA,IACAV;AAAA,IACAM;AAAA,EACF;AAEJ,GAGME,IAAiB,CACrBH,GACAK,GACAH,GACAD,MACS;AAEH,QAAAK,IAAcZ,EAASW,CAAS,GAChCE,IAAgBD,KACnBA,EAAY,IAAIN,CAA8B,GAC3CS,IAAeF,KACnBA,EAAc,IAAIL,CAA8C,GAG5DQ,IAAeD,MAAiB,SAAYA,IAAeR;AAIjE,EACEM,KACAA,EAAc,IAAIL,CAA8C,KAEhEK,EAAc,OAAOL,CAA8C,GAGjEI,MAAgB,CAACC,KAAiB,CAACA,EAAc,SACnDD,EAAY,OAAON,CAA8B,IAG/C,CAACM,KAAe,CAACA,EAAY,SAAM,OAAOZ,EAASW,CAAS,IAI5D,CAACE,KAAiB,CAACA,EAAc,SAClCP,EAAgC;AAAA,IAC/BK;AAAA,IACAV;AAAA,IACAe;AAAA,EACF;AAEJ,GAGMC,IAAyBP,GACzBQ,IAA6BT;"}