{"version":3,"file":"click-analytics-snippet.min.js","mappings":"YAIA,SAASA,EAA4BC,GACnC,MAAMC,EAAUD,EAAQC,QAAQ,sBAEhC,GAAIA,EACF,OAAOA,EAGT,MAAMC,EAAOF,EAAQG,cAGrB,OAAiBD,aASOE,WARfL,EAA4BG,EAAKG,WAD1C,CAGF,CA2CA,SAASC,EAAQC,GAAmB,IAAAC,EAKlC,MAAMC,EAAgC,OAA1BD,EAAGD,EAAMG,eAAe,IAAEF,EAAID,EAAMI,OAChD,GAAIF,aAAkBG,QAAS,CAC7B,MAAMZ,EAAUD,EAA4BU,GAC5C,GAAIT,aAAmBa,aAAeb,EAAQc,QAAQC,WAAY,CAEhE,MAAMC,EAAsB,IAAIC,YAAoB,kBAAmB,CACrEC,SAAS,EACTC,UAAU,EACVC,OAAQ,CACNC,KAAMrB,EAAQc,QAAQC,cAG1Bf,EAAQsB,cAAcN,EACxB,CACF,CACF,CA8BAO,SAASC,oBAAoB,QAASC,gBAAgBC,UAEtD,IAAK,MAAMnB,KAASkB,gBAAgBE,MAClCrB,EAAQC,GAGVkB,gBAAgBE,MAAQ,GAExBJ,SAASK,iBAAiB,QAAStB","sources":["webpack://lange/./snippets/click-analytics/index.ts"],"sourcesContent":["/**\n * Finds the closest ancestor element (including the element itself) that has the `data-click-track`\n * attribute. It also traverses through Shadow DOMs boundaries if necessary.\n */\nfunction findClosestTrackableElement(element: Element) {\n  const closest = element.closest('[data-click-track]');\n\n  if (closest) {\n    return closest;\n  }\n\n  const root = element.getRootNode();\n  // under some circumstances `getRootNode()` may return the document, the element itself etc we are\n  // only interested in cases where the root is a custom element's ShadowRoot\n  if (isShadowRoot(root)) {\n    return findClosestTrackableElement(root.host);\n  }\n}\n\n/**\n * Type guard to check if a node is a ShadowRoot\n */\nfunction isShadowRoot(value: any): value is ShadowRoot {\n  return value instanceof ShadowRoot;\n}\n\ntype ElementClickedEvent = CustomEvent<{\n  /**\n   * Identifiable name of the clicked element (read from data-click-track attribute)\n   */\n  name: string;\n}>;\n\n/**\n * Handles click events and dispatches a custom element-clicked event if the clicked element (or its\n * ancestor) has the data-click-track attribute.\n *\n * data-click-track is a custom attribute added exclusively for the purposes of click analytics\n * tracking. This code maintains backward compatibility by dispatching a custom event to be tracked\n * by other analytics systems.\n *\n * The reason this does not leverage the builtin \"clicked\" standard event for Shopify web pixels is\n * because (a) we would be restricted to the set of attributes that Shopify provides and as a result\n * would effectively be limited to a hackish use of the name attribute and (b) Shopify's code that\n * detects the element does not pierce shadow dom boundaries. We have several elements that we wish\n * to track that are located within shadow doms. The abuse of name is tolerable but the shadow dom\n * issue is not.\n *\n * The reason we are using a custom event and not a standard event where listeners can simply check\n * the attributes of the event target is to snapshot the element's identifiable name at the time of\n * the click. If we were to just use a plain event that read the attribute from the event target\n * later, then there is a risk that the attribute's value has changed between the time of the click\n * and the time the event is handled. It also simplifies the relay to Shopify web pixels later that\n * can just forward the event as is.\n *\n * While we could dispatch on window directly, we prefer to dispatch from the trackable element\n * itself so the event's target is the actual element that was clicked. bubbles lets the event\n * propagate up to the window-level listeners that consume it (e.g. the Shopify analytics bridge),\n * and composed lets it cross any shadow DOM boundaries the trackable element may live behind.\n */\nfunction onClick(event: MouseEvent) {\n  // By the time a click reaches the document listener, its target has been retargeted to the\n  // outermost shadow host, so the start of the composed path is what was actually clicked. A click\n  // replayed from the bootstrap queue has finished dispatching and has an empty path, so that falls\n  // back to the target.\n  const origin = event.composedPath()[0] ?? event.target;\n  if (origin instanceof Element) {\n    const element = findClosestTrackableElement(origin);\n    if (element instanceof HTMLElement && element.dataset.clickTrack) {\n      type Detail = ElementClickedEvent['detail'];\n      const elementClickedEvent = new CustomEvent<Detail>('element-clicked', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          name: element.dataset.clickTrack\n        }\n      });\n      element.dispatchEvent(elementClickedEvent);\n    }\n  }\n}\n\ndeclare global {\n  /**\n   * Holds the global state for click analytics. Created in the snippet liquid before this module\n   * loads so that clicks can be captured and replayed once the module initializes.\n   */\n  var click_analytics: {\n    /**\n     * Captures clicks into the queue before the script is loaded. This exists only so this module\n     * can stop the bootstrap capture once it takes over; it is not meant to be used anywhere other\n     * than the snippet liquid.\n     */\n    listener: (event: MouseEvent) => void;\n\n    /**\n     * Stores click events captured before the script is loaded.\n     */\n    queue: MouseEvent[];\n  };\n\n  interface GlobalEventHandlersEventMap {\n    'element-clicked': ElementClickedEvent;\n  }\n}\n\n// Stop the bootstrap listener from capturing any further clicks before we drain. From this point on\n// nothing new is appended to the queue, so the drain below sees a stable set and the queue can be\n// released. Without this the bootstrap listener keeps pushing into a queue that is never read\n// again, leaking every click event for the lifetime of the page.\ndocument.removeEventListener('click', click_analytics.listener);\n\nfor (const event of click_analytics.queue) {\n  onClick(event);\n}\n\nclick_analytics.queue = [];\n\ndocument.addEventListener('click', onClick);\n"],"names":["findClosestTrackableElement","element","closest","root","getRootNode","ShadowRoot","host","onClick","event","_event$composedPath$","origin","composedPath","target","Element","HTMLElement","dataset","clickTrack","elementClickedEvent","CustomEvent","bubbles","composed","detail","name","dispatchEvent","document","removeEventListener","click_analytics","listener","queue","addEventListener"],"sourceRoot":""}