{
  "version": 3,
  "sources": ["map_region_panel.js"],
  "sourcesContent": ["/*\n * static/js/map_region_panel.js \u2014 the region label in search, the pin\n * roundel and the pinned-regions list (SNOW-801, SNOW-814; the panel itself\n * and the visible chip retired by SNOW-1173).\n *\n * The file keeps its SNOW-801 name, though the panel it was named for is\n * gone. Until SNOW-1173 the #region-readout chip at the map's top-left\n * disclosed a dropdown, #region-panel, carrying the region's summary, its\n * resorts, an \"Open bulletin for <date>\" link and the user's pinned regions.\n * On a phone the chip was already hidden and every selection opened the\n * region sheet (static/js/map_region_sheet.js), which carries the same facts\n * in full; on a desktop the dropdown was a second, smaller copy of that\n * sheet, and a region tap opened nothing while a resort or a weather tap\n * opened the inset panel. Hugo, 21 Oct: \"Weather and Resort both open the\n * overlay on desktop, but Region still opens the small dropdown top left.\"\n *\n * So the sheet now opens on every width, and what is left here is:\n *\n *   - THE CHIP IS FOLDED INTO SEARCH. With the dropdown gone the chip was a\n *     label, so on a desktop it moved into the search box, which moved to\n *     the top-left where the chip was (Hugo, 21 Oct: \"If the region chip is\n *     essentially a label, can we combine it into the search box\"). The\n *     ribbon that held the chip, its star and its download is not drawn on\n *     any width now \u2014 the region sheet's header carries the star and the\n *     download \u2014 but it stays in the DOM: map.js and map_season_ribbon.js\n *     still write the selection's name and the day's swatch into\n *     #region-readout, and this module mirrors those into the search box's\n *     #search-region-label, so the two writers did not need a second\n *     target. A press on the label opens search (map.js), whose empty field\n *     lists the pinned regions; picking the region already selected reopens\n *     its sheet.\n *\n *   - THE PIN ROUNDEL (SNOW-814) \u2014 the star in the (undrawn) ribbon header\n *     (public/partials/_map_region_pin_control.html), which the region\n *     sheet's header proxies on every width. It lives with the pinned list\n *     because the roundel's state and the list are the SAME fact: \"is this\n *     region in my list\". One loader, one derivation, and they cannot\n *     drift.\n *\n *   - THE PINNED-REGIONS LIST, loaded over HTMX from favourites:region_list\n *     into a hidden host in the ribbon (#region-pin-rows) and published as\n *     window.pwaPinnedRegions, which search lists before anything is typed.\n *     That was the phone's arrangement from 16 Oct (search replaced the chip\n *     there); SNOW-1173 made it every width's, so a desktop finds its pinned\n *     regions where a phone does, and picking one selects it and opens the\n *     sheet like any other selection.\n */\n\n(function regionPanelInit() {\n  'use strict';\n\n  const chip = document.getElementById('region-readout');\n  const pinnedRows = document.getElementById('region-pin-rows');\n  if (!chip || !pinnedRows) return;\n\n  // SNOW-814: absent for a visitor \u2014 there is no list to load.\n  const PIN_LIST_URL = pinnedRows.dataset.regionPinListUrl || '';\n  // The toggle endpoint with the codebase's 'XX-0000' region-id token in it.\n  const PIN_TOGGLE_URL_TEMPLATE = pinnedRows.dataset.regionPinToggleUrl || '';\n  const pinControl = document.getElementById('map-region-pin-control');\n  const label = document.getElementById('search-region-label');\n\n  /** @type {?string} The region the chip names, or null. */\n  let currentRegionId = null;\n\n  /**\n   * Read Django's CSRF cookie.\n   *\n   * A local copy rather than a shared helper because this is the only\n   * write this module makes; static/js/favourites.js keeps its own for the\n   * same reason.\n   *\n   * @returns {string} The token, or '' when the cookie is absent.\n   */\n  const readCsrfToken = () => {\n    const match = document.cookie.match(/(?:^|;\\s*)csrftoken=([^;]*)/);\n    return match ? decodeURIComponent(match[1]) : '';\n  };\n\n  // Server-translated strings. `makemessages` does not scan JavaScript, so a\n  // literal here would ship as English to every locale \u2014 the i18n-lint rule.\n  const STRINGS = self.pwaStrings.read('region-panel-strings-template', {\n    'pin-region': 'Pin this region',\n    'unpin-region': 'Unpin this region',\n    'pin-failed': \"That region couldn't be pinned. Try again.\",\n  });\n\n  /**\n   * Whether the loaded list holds a row for ``regionId``.\n   *\n   * The list IS the state. Reading it rather than tracking a flag is what\n   * keeps the roundel and the list from disagreeing: an unpin, a pin and a\n   * reload all change one thing.\n   *\n   * @param {?string} regionId An EAWS micro-region id, or null.\n   * @returns {boolean} True when the region is pinned.\n   */\n  const isPinned = (regionId) => {\n    if (!regionId) return false;\n    // Compared as attribute VALUES rather than built into a selector: a\n    // region id in a selector string needs CSS.escape, which jsdom does not\n    // implement.\n    return [...pinnedRows.querySelectorAll('[data-row-focus-region]')].some(\n      (button) => button.getAttribute('data-row-focus-region') === regionId,\n    );\n  };\n\n  /**\n   * Put the pin roundel into the state the current selection implies.\n   *\n   * Three states, and the disabled one is honest rather than hidden: with\n   * no region selected there is nothing to pin, so the control says so and\n   * refuses the press \u2014 the same treatment the download roundel beside it\n   * gives the same situation.\n   *\n   * A visitor's control is an <a> to the sign-in page rendered in its\n   * 'signin' state, which nothing here touches.\n   *\n   * @returns {void}\n   */\n  const syncPinControl = () => {\n    if (!pinControl || pinControl.dataset.pinState === 'signin') return;\n    if (!currentRegionId) {\n      pinControl.dataset.pinState = 'no-region';\n      pinControl.setAttribute('aria-disabled', 'true');\n      pinControl.setAttribute('aria-pressed', 'false');\n      return;\n    }\n    const pinned = isPinned(currentRegionId);\n    pinControl.dataset.pinState = pinned ? 'pinned' : 'idle';\n    pinControl.setAttribute('aria-disabled', 'false');\n    pinControl.setAttribute('aria-pressed', pinned ? 'true' : 'false');\n    const label = pinned ? STRINGS['unpin-region'] : STRINGS['pin-region'];\n    pinControl.setAttribute('aria-label', label);\n    pinControl.setAttribute('title', label);\n  };\n\n  /**\n   * (Re)load the pinned-regions rows from favourites:region_list.\n   *\n   * Over HTMX rather than `fetch` + innerHTML because the endpoint is\n   * behind `@require_htmx` (invariant 4), and its rows are the server's own\n   * (includes/_ugc_panel_row.html), so the ids read off them are the list\n   * the user would see anywhere else.\n   *\n   * @returns {Promise<void>} Settles once the rows are in the document.\n   */\n  const loadPins = () => {\n    if (!PIN_LIST_URL || typeof htmx === 'undefined') return Promise.resolve();\n    // `htmx.ajax` resolves once the swap has settled.\n    return htmx\n      .ajax('GET', PIN_LIST_URL, { target: pinnedRows, swap: 'innerHTML' })\n      .then(() => {\n        syncPinControl();\n      });\n  };\n\n  /**\n   * Copy what the chip says onto search's region label: the selection's\n   * name and the day's danger swatch, or the search prompt with nothing\n   * selected.\n   *\n   * The chip stays the one place map.js and map_season_ribbon.js write to\n   * (see the header); this follows it rather than repeating their logic.\n   *\n   * @returns {void}\n   */\n  const mirrorLabel = () => {\n    if (!label) return;\n    const name = label.querySelector('[data-search-region-name]');\n    const swatch = /** @type {HTMLElement|null} */ (\n      label.querySelector('[data-search-region-swatch]')\n    );\n    const hasRegion = chip.classList.contains('has-region');\n    const leaf = chip.querySelector('.region-readout-leaf')?.textContent || '';\n    const chipSwatch = /** @type {HTMLElement|null} */ (\n      chip.querySelector('.region-readout-swatch')\n    );\n    label.classList.toggle('has-region', hasRegion);\n    if (name) name.textContent = hasRegion ? leaf : label.dataset.prompt || '';\n    if (swatch) {\n      swatch.hidden = !hasRegion;\n      swatch.style.background = hasRegion && chipSwatch ? chipSwatch.style.background : '';\n    }\n  };\n  if (label && typeof MutationObserver === 'function') {\n    new MutationObserver(mirrorLabel).observe(chip, {\n      attributes: true,\n      attributeFilter: ['class', 'style'],\n      characterData: true,\n      childList: true,\n      subtree: true,\n    });\n  }\n\n  document.addEventListener('snowdesk:region-selected', (ev) => {\n    currentRegionId = ev.detail?.region_id || null;\n    syncPinControl();\n    // The region sheet opens with this selection and carries the star, so\n    // a list that failed to load is retried now rather than left wrong.\n    ensurePins();\n  });\n\n  // The pin roundel. Online-only and outside the mutation queue, like the\n  // pill it replaced: a region pin has no coordinate, so there is nothing to\n  // draw optimistically and nothing for a replay to land in.\n  //\n  // The roundel flips from the RESPONSE and the list reloads behind it. The\n  // response is the authority either way \u2014 it is the write that just\n  // happened \u2014 and taking it directly is what makes the control answer on\n  // the same frame as the press rather than a request later.\n  pinControl?.addEventListener('click', (ev) => {\n    if (pinControl.tagName === 'A') return;\n    ev.preventDefault();\n    if (!currentRegionId || pinControl.dataset.pinBusy === 'true') return;\n    if (!PIN_TOGGLE_URL_TEMPLATE) return;\n    pinControl.dataset.pinBusy = 'true';\n    fetch(PIN_TOGGLE_URL_TEMPLATE.replace('XX-0000', encodeURIComponent(currentRegionId)), {\n      method: 'POST',\n      headers: {\n        // favourite_region_toggle is @require_htmx; this is a plain fetch.\n        'HX-Request': 'true',\n        'X-CSRFToken': readCsrfToken(),\n      },\n    })\n      .then((resp) => {\n        if (!resp.ok) throw new Error('region pin ' + resp.status);\n        return resp.json();\n      })\n      .then((data) => {\n        delete pinControl.dataset.pinBusy;\n        pinControl.dataset.pinState = data.pinned ? 'pinned' : 'idle';\n        pinControl.setAttribute('aria-pressed', data.pinned ? 'true' : 'false');\n        const label = data.pinned ? STRINGS['unpin-region'] : STRINGS['pin-region'];\n        pinControl.setAttribute('aria-label', label);\n        pinControl.setAttribute('title', label);\n        window.pwaTelemetry?.emit('map.region.pin_toggled', { pinned: !!data.pinned });\n        loadPins();\n      })\n      .catch(() => {\n        delete pinControl.dataset.pinBusy;\n        // Put the control back to what the list still says, so a failed\n        // toggle cannot leave a star claiming a pin that was never written.\n        syncPinControl();\n        window.MapSheet?.toast?.(STRINGS['pin-failed']);\n      });\n  });\n\n  // Search lists the pinned regions before anything is typed. It reads the\n  // rows this module loaded rather than fetching its own copy, so the list\n  // in search and the pin roundel are one fact. `ids` is in list order;\n  // `ready` settles once the first load has.\n  //\n  // A failed load is retried on the next ask. Nothing else ever reloads the\n  // list once boot is past \u2014 the chip's panel retried on each open until\n  // SNOW-1173 retired it \u2014 so a transient failure at boot would otherwise\n  // leave search with no pinned regions all session, and the star offering\n  // to pin a region that is pinned (whose toggle would unpin it).\n  /** @type {Promise<void>} */\n  let pinsReady = Promise.resolve();\n  // True once a load has come back 2xx; htmx resolves its promise for an\n  // error response too, so the request's own outcome is what says.\n  let pinsLoaded = false;\n  let pinsLoading = false;\n  pinnedRows.addEventListener('htmx:afterRequest', (ev) => {\n    pinsLoaded = pinsLoaded || !!ev.detail?.successful;\n  });\n  const ensurePins = () => {\n    if (!PIN_LIST_URL || pinsLoaded || pinsLoading) return pinsReady;\n    pinsLoading = true;\n    pinsReady = loadPins()\n      .catch(() => {})\n      .finally(() => {\n        pinsLoading = false;\n      });\n    return pinsReady;\n  };\n  window.pwaPinnedRegions = Object.freeze({\n    ids: () =>\n      [...pinnedRows.querySelectorAll('[data-row-focus-region]')]\n        .map((button) => button.getAttribute('data-row-focus-region'))\n        .filter(Boolean),\n    ready: ensurePins,\n  });\n\n  mirrorLabel();\n  // Load the list at boot for a signed-in user, not on first use: the pin\n  // roundel is in the header from the first paint and its state comes from\n  // this list, so deferring the fetch would leave the star lying about\n  // whichever region the page opened on.\n  ensurePins();\n  syncPinControl();\n}());\n"],
  "mappings": "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",
  "names": ["chip", "pinnedRows", "PIN_LIST_URL", "PIN_TOGGLE_URL_TEMPLATE", "pinControl", "label", "currentRegionId", "readCsrfToken", "match", "STRINGS", "isPinned", "regionId", "button", "syncPinControl", "pinned", "loadPins", "mirrorLabel", "name", "swatch", "hasRegion", "leaf", "chipSwatch", "ev", "ensurePins", "resp", "data", "pinsReady", "pinsLoaded", "pinsLoading"]
}
