{
  "version": 3,
  "sources": ["overflow_menu.js"],
  "sourcesContent": ["/*\n * static/js/overflow_menu.js \u2014 reusable \"\u2026\" kebab overflow menu (SNOW-645,\n * re-adopted and fixed by SNOW-830).\n *\n * The DOM half of includes/_overflow_menu.html \u2014 see that partial's own\n * header for why this exists and what it replaces.\n *\n * Delegated and instance-agnostic: ANY number of [data-overflow-menu]\n * wrappers on the page (one per route row, today) are driven by the ONE\n * set of listeners below \u2014 there is no per-instance JS to bind, which\n * matters because static/js/routes.js re-clones the whole sheet body on\n * every open (and map_downloads_manager.js re-clones every row on every\n * render()), discarding any listener bound to the previous copy. A\n * per-instance IIFE closure \u2014 the shape static/js/map_basemap_picker.js's\n * own (single, hardcoded #basemap-menu) popover uses \u2014 would have to be\n * rebound after every render.\n *\n * Mirrors that same popover's open/outside-click/Escape behaviour,\n * generalised from one hardcoded element to \"whichever\n * [data-overflow-menu] the event landed in or near\". At most one menu is\n * ever open at once, matching every other popover in this app.\n *\n * Loaded once from apps/public/templates/public/partials/_routes_surface.html,\n * next to share.js / routes.js \u2014 the scripts that surface needs and no\n * other page does. (It claimed to load from _map_downloads_sheet.html for\n * its whole life; nothing loaded it anywhere, because SNOW-658 dropped\n * the ellipsis menu from every panel the day after SNOW-645 built it.\n * SNOW-830 brought it back for the route row alone \u2014 see\n * includes/_overflow_menu.html's header for which panels have one and\n * which keep their visible trash.)\n *\n * THREE THINGS SNOW-830 HAD TO FIX before the primitive could be used on\n * a map panel at all. Each is fixed HERE rather than worked around at the\n * call site, because each is a property of the menu and not of the route\n * row:\n *\n *   1. POSITION IS `fixed`, MEASURED ON OPEN \u2014 not the `absolute right-0`\n *      the partial used to carry. Two clipping ancestors sit between a\n *      panel row and the viewport: includes/_ugc_panel.html's\n *      `overflow-y-auto` scroll container (declaring `overflow-y`\n *      computes `overflow-x` to `auto` as well, so it clips on both axes)\n *      and includes/_overlay_sheet.html's `overflow-hidden` sheet. An\n *      absolutely-positioned menu on the last row of a long list has\n *      nowhere to go. `fixed` escapes both because neither establishes a\n *      containing block \u2014 the sheet is plain `fixed` + `overflow-hidden`\n *      with no `transform`, `filter`, `backdrop-filter` or `contain`\n *      (`--shadow-glass` is a box-shadow, which creates none).\n *\n *   2. IT CLOSES ON SCROLL AND RESIZE. The menu is anchored to a row that\n *      is moving away, and a `fixed` box does not move with it. Closing\n *      beats repositioning: the user scrolled, so the row they opened the\n *      menu on is no longer the row under it. `scroll` does not bubble,\n *      so the listener is registered with `{capture: true}` on `document`\n *      to catch the rows container's own scrolling.\n *\n *   3. ESCAPE IS BOUND IN CAPTURE PHASE. static/js/map_sheet.js binds its\n *      own Escape-closes-the-sheet handler on `document` in BUBBLE phase,\n *      so a bubble-phase listener here could not stop it \u2014 both are on\n *      the same node and `stopPropagation` between two listeners on one\n *      node in one phase does nothing. Capture runs before every bubble\n *      listener on the way down, so an Escape that closes a menu can call\n *      `stopPropagation()` and the sheet never hears it. The call is made\n *      ONLY when a menu is actually open, so an Escape with none open\n *      falls through untouched and still closes the sheet.\n */\n\n(function overflowMenuInit() {\n  'use strict';\n\n  var OPEN_ATTR = 'data-overflow-open';\n\n  // The gap between the trigger and the menu, in CSS pixels \u2014 the 4px the\n  // partial's own `mt-1` used to draw, now that JS owns the position. Also\n  // the minimum clearance kept from every viewport edge.\n  var GAP = 4;\n\n  /**\n   * @param {Element} wrapper A `[data-overflow-menu]` element.\n   * @returns {HTMLButtonElement|null}\n   */\n  function trigger(wrapper) {\n    return wrapper.querySelector('[data-overflow-trigger]');\n  }\n\n  /**\n   * @param {Element} wrapper A `[data-overflow-menu]` element.\n   * @returns {HTMLElement|null}\n   */\n  function menu(wrapper) {\n    return wrapper.querySelector('[role=\"menu\"]');\n  }\n\n  /**\n   * Place an open menu against its trigger, in viewport coordinates.\n   *\n   * Right edge to right edge: the trigger is the last thing in a row, so\n   * a menu hanging leftwards from it stays inside the panel while one\n   * hanging rightwards would immediately leave it. Below the trigger by\n   * preference, flipped above when the menu would not fit below \u2014 which\n   * is the case this whole function exists for, the last row of a full\n   * panel.\n   *\n   * Called AFTER the menu is unhidden, because a `hidden` element has no\n   * box to measure.\n   *\n   * @param {HTMLElement} triggerEl The `[data-overflow-trigger]` button.\n   * @param {HTMLElement} menuEl The `[role=\"menu\"]` element, already shown.\n   * @returns {void}\n   */\n  function place(triggerEl, menuEl) {\n    // TAKE THE MENU OUT OF FLOW BEFORE MEASURING ANYTHING (SNOW-886). This\n    // assignment used to sit BELOW the `getBoundingClientRect()` call, and\n    // that one line of ordering was the whole \"the menu opens 132px to the\n    // left of its trigger\" bug.\n    //\n    // `setOpen` un-hides the <ul> and then calls this. At that instant the\n    // menu is visible and still `position: static`, so it is in normal flow\n    // inside includes/_overflow_menu.html's `<div class=\"inline-block\"\n    // data-overflow-menu>` wrapper \u2014 and that wrapper therefore widens from\n    // the trigger's 44x44 to the menu's own `w-44`, 176px. The row is a flex\n    // line whose label is `flex-1`, so the extra 132px (176 - 44) is taken\n    // out of the label and the trigger is pushed exactly that far LEFT.\n    // Reading the trigger's rect first forces layout in precisely that\n    // transient state; `position: fixed` then landed a line later, the row\n    // snapped back, and the menu was placed against an anchor that had\n    // already stopped being true. It predicted the symptom exactly: three\n    // screenshots at different sheet and trigger positions all showed a\n    // CONSTANT 131-133px horizontal error with the vertical correct, because\n    // widening the wrapper moves the trigger sideways and not up or down.\n    //\n    // So: out of flow first, measure second. Do not reorder these.\n    menuEl.style.position = 'fixed';\n    // Cleared first so a re-open measures the menu's own natural box\n    // rather than the one the previous placement squeezed it into.\n    menuEl.style.top = '';\n    menuEl.style.left = '';\n\n    var anchor = triggerEl.getBoundingClientRect();\n    var width = menuEl.offsetWidth;\n    var height = menuEl.offsetHeight;\n    var viewportWidth = window.innerWidth || 0;\n    var viewportHeight = window.innerHeight || 0;\n\n    var left = anchor.right - width;\n    if (left < GAP) left = GAP;\n    if (viewportWidth && left + width > viewportWidth - GAP) {\n      left = Math.max(GAP, viewportWidth - GAP - width);\n    }\n\n    var top = anchor.bottom + GAP;\n    var fitsBelow = !viewportHeight || top + height <= viewportHeight - GAP;\n    if (!fitsBelow) {\n      var above = anchor.top - GAP - height;\n      // Flip only if there is genuinely more room above: on a viewport\n      // too short for the menu either way, hanging off the bottom (where\n      // the page can still be scrolled) beats hanging off the top.\n      if (above >= GAP) top = above;\n    }\n\n    menuEl.style.top = top + 'px';\n    menuEl.style.left = left + 'px';\n  }\n\n  /**\n   * @param {Element} wrapper A `[data-overflow-menu]` element.\n   * @param {boolean} open\n   * @returns {void}\n   */\n  function setOpen(wrapper, open) {\n    var t = trigger(wrapper);\n    var m = menu(wrapper);\n    if (!t || !m) return;\n    if (open) {\n      wrapper.setAttribute(OPEN_ATTR, '');\n    } else {\n      wrapper.removeAttribute(OPEN_ATTR);\n    }\n    t.setAttribute('aria-expanded', open ? 'true' : 'false');\n    m.hidden = !open;\n    if (open) {\n      place(t, m);\n    } else {\n      // Leave nothing behind: the next open re-measures from scratch, and\n      // a closed menu carrying last time's coordinates is a trap for\n      // anyone reading the DOM.\n      m.style.position = '';\n      m.style.top = '';\n      m.style.left = '';\n    }\n  }\n\n  /**\n   * Close every open menu except `except` \u2014 at most one is ever open at\n   * once, matching every other popover in this app (the basemap picker,\n   * the layers menu).\n   *\n   * @param {Element|null} except\n   * @returns {void}\n   */\n  function closeAllExcept(except) {\n    var open = document.querySelectorAll('[data-overflow-menu][' + OPEN_ATTR + ']');\n    for (var i = 0; i < open.length; i += 1) {\n      if (open[i] !== except) setOpen(open[i], false);\n    }\n  }\n\n  /**\n   * @returns {Element|null} The one open menu's wrapper, if any.\n   */\n  function openWrapper() {\n    return document.querySelector('[data-overflow-menu][' + OPEN_ATTR + ']');\n  }\n\n  // Click (not pointerdown), same reasoning as the basemap picker's own\n  // outside-click handler: an item selection inside an open menu has to\n  // fire ITS OWN handler (routes.js's delegated share/rename handling,\n  // bound on the sheet \u2014 a descendant of document, so it runs first as\n  // the event bubbles) before this one closes the menu out from under it.\n  document.addEventListener('click', function (event) {\n    var target = /** @type {HTMLElement} */ (event.target);\n    if (!target || !target.closest) return;\n\n    var openTrigger = target.closest('[data-overflow-trigger]');\n    if (openTrigger) {\n      var wrapper = openTrigger.closest('[data-overflow-menu]');\n      if (!wrapper) return;\n      var willOpen = !wrapper.hasAttribute(OPEN_ATTR);\n      // Only one open at a time: opening this one closes every other;\n      // closing this one (a second click on its own trigger) just closes\n      // everything, itself included.\n      closeAllExcept(willOpen ? wrapper : null);\n      setOpen(wrapper, willOpen);\n      return;\n    }\n\n    // Anything else \u2014 an item inside an open menu (handled by the\n    // caller's own listener first, per the comment above) or genuinely\n    // outside \u2014 closes every open menu.\n    closeAllExcept(null);\n  });\n\n  // Capture phase, and stopPropagation only when there is something to\n  // close \u2014 see reason 3 in this module's header. Without it, one Escape\n  // closes the menu AND the sheet the menu sits in.\n  document.addEventListener(\n    'keydown',\n    function (event) {\n      if (event.key !== 'Escape') return;\n      var open = openWrapper();\n      if (!open) return;\n      event.stopPropagation();\n      setOpen(open, false);\n      var t = trigger(open);\n      if (t) t.focus();\n    },\n    true\n  );\n\n  // A `fixed` menu does not travel with the row it was opened from, so\n  // both of these close it rather than re-place it. `scroll` does not\n  // bubble: capture is what catches the panel's own scroll container\n  // rather than only a scroll of the document.\n  document.addEventListener(\n    'scroll',\n    function () {\n      if (openWrapper()) closeAllExcept(null);\n    },\n    true\n  );\n\n  window.addEventListener('resize', function () {\n    if (openWrapper()) closeAllExcept(null);\n  });\n})();\n"],
  "mappings": "CAkEC,UAA4B,CAC3B,aAEA,IAAIA,EAAY,qBAKZC,EAAM,EAMV,SAASC,EAAQC,EAAS,CACxB,OAAOA,EAAQ,cAAc,yBAAyB,CACxD,CAMA,SAASC,EAAKD,EAAS,CACrB,OAAOA,EAAQ,cAAc,eAAe,CAC9C,CAmBA,SAASE,EAAMC,EAAWC,EAAQ,CAsBhCA,EAAO,MAAM,SAAW,QAGxBA,EAAO,MAAM,IAAM,GACnBA,EAAO,MAAM,KAAO,GAEpB,IAAIC,EAASF,EAAU,sBAAsB,EACzCG,EAAQF,EAAO,YACfG,EAASH,EAAO,aAChBI,EAAgB,OAAO,YAAc,EACrCC,EAAiB,OAAO,aAAe,EAEvCC,EAAOL,EAAO,MAAQC,EACtBI,EAAOZ,IAAKY,EAAOZ,GACnBU,GAAiBE,EAAOJ,EAAQE,EAAgBV,IAClDY,EAAO,KAAK,IAAIZ,EAAKU,EAAgBV,EAAMQ,CAAK,GAGlD,IAAIK,EAAMN,EAAO,OAASP,EACtBc,EAAY,CAACH,GAAkBE,EAAMJ,GAAUE,EAAiBX,EACpE,GAAI,CAACc,EAAW,CACd,IAAIC,EAAQR,EAAO,IAAMP,EAAMS,EAI3BM,GAASf,IAAKa,EAAME,EAC1B,CAEAT,EAAO,MAAM,IAAMO,EAAM,KACzBP,EAAO,MAAM,KAAOM,EAAO,IAC7B,CAOA,SAASI,EAAQd,EAASe,EAAM,CAC9B,IAAIC,EAAIjB,EAAQC,CAAO,EACnBiB,EAAIhB,EAAKD,CAAO,EAChB,CAACgB,GAAK,CAACC,IACPF,EACFf,EAAQ,aAAaH,EAAW,EAAE,EAElCG,EAAQ,gBAAgBH,CAAS,EAEnCmB,EAAE,aAAa,gBAAiBD,EAAO,OAAS,OAAO,EACvDE,EAAE,OAAS,CAACF,EACRA,EACFb,EAAMc,EAAGC,CAAC,GAKVA,EAAE,MAAM,SAAW,GACnBA,EAAE,MAAM,IAAM,GACdA,EAAE,MAAM,KAAO,IAEnB,CAUA,SAASC,EAAeC,EAAQ,CAE9B,QADIJ,EAAO,SAAS,iBAAiB,wBAA0BlB,EAAY,GAAG,EACrEuB,EAAI,EAAGA,EAAIL,EAAK,OAAQK,GAAK,EAChCL,EAAKK,CAAC,IAAMD,GAAQL,EAAQC,EAAKK,CAAC,EAAG,EAAK,CAElD,CAKA,SAASC,GAAc,CACrB,OAAO,SAAS,cAAc,wBAA0BxB,EAAY,GAAG,CACzE,CAOA,SAAS,iBAAiB,QAAS,SAAUyB,EAAO,CAClD,IAAIC,EAAqCD,EAAM,OAC/C,GAAI,GAACC,GAAU,CAACA,EAAO,SAEvB,KAAIC,EAAcD,EAAO,QAAQ,yBAAyB,EAC1D,GAAIC,EAAa,CACf,IAAIxB,EAAUwB,EAAY,QAAQ,sBAAsB,EACxD,GAAI,CAACxB,EAAS,OACd,IAAIyB,EAAW,CAACzB,EAAQ,aAAaH,CAAS,EAI9CqB,EAAeO,EAAWzB,EAAU,IAAI,EACxCc,EAAQd,EAASyB,CAAQ,EACzB,MACF,CAKAP,EAAe,IAAI,EACrB,CAAC,EAKD,SAAS,iBACP,UACA,SAAUI,EAAO,CACf,GAAIA,EAAM,MAAQ,SAClB,KAAIP,EAAOM,EAAY,EACvB,GAAKN,EACL,CAAAO,EAAM,gBAAgB,EACtBR,EAAQC,EAAM,EAAK,EACnB,IAAIC,EAAIjB,EAAQgB,CAAI,EAChBC,GAAGA,EAAE,MAAM,GACjB,EACA,EACF,EAMA,SAAS,iBACP,SACA,UAAY,CACNK,EAAY,GAAGH,EAAe,IAAI,CACxC,EACA,EACF,EAEA,OAAO,iBAAiB,SAAU,UAAY,CACxCG,EAAY,GAAGH,EAAe,IAAI,CACxC,CAAC,CACH,GAAG",
  "names": ["OPEN_ATTR", "GAP", "trigger", "wrapper", "menu", "place", "triggerEl", "menuEl", "anchor", "width", "height", "viewportWidth", "viewportHeight", "left", "top", "fitsBelow", "above", "setOpen", "open", "t", "m", "closeAllExcept", "except", "i", "openWrapper", "event", "target", "openTrigger", "willOpen"]
}
