{
  "version": 3,
  "sources": ["sheet_detents_core.js"],
  "sourcesContent": ["// @ts-check\n/*\n * static/js/sheet_detents_core.js \u2014 where a phone map sheet settles after a\n * drag (SNOW-1097).\n *\n * Below the `sm` breakpoint a sheet that declares detents\n * (includes/_overlay_sheet.html's `detents`) rests at one of up to three\n * heights: PEEK (a short summary, the map left clear), HALF (the detail\n * beside the ground) and FULL (the map covered, the body scrolls). A drag on\n * the handle moves the sheet freely; when the finger lifts, the sheet has to\n * pick one of them. This module is that choice, and nothing else: pure\n * functions of numbers and detent names, no DOM, so the rules are tested in\n * Vitest (tests/js/test_sheet_detents_core.js) and map_sheet.js only binds\n * them to pointer events.\n *\n * THE RULE. The sheet settles at the declared detent nearest to where it\n * would have come to rest had the flick carried on: the release height\n * projected forward by the release velocity over `PROJECTION_MS`. A slow\n * drag therefore lands on the nearest detent, and a quick flick carries past\n * a detent it was still short of \u2014 the behaviour readers know from Apple and\n * Google Maps. A projection that falls below `CLOSE_FRACTION` of the lowest\n * declared detent closes the sheet instead, which is how a drag down\n * dismisses it.\n *\n * Heights are in CSS pixels measured from the bottom of the sheet, so a\n * larger number is a taller sheet. Velocity is in pixels per millisecond and\n * positive while the sheet GROWS (the finger moving up).\n */\n\n(function sheetDetentsCoreInit() {\n  'use strict';\n\n  /** The order detents rise in. A sheet declares a subset of these. */\n  var ORDER = Object.freeze(['peek', 'half', 'full']);\n\n  /**\n   * How far ahead a release velocity is projected, in milliseconds. Long\n   * enough that a deliberate flick from peek reaches half, short enough\n   * that a slow drag that stops between two detents is not thrown on.\n   */\n  var PROJECTION_MS = 150;\n\n  /**\n   * A release projected below this fraction of the lowest detent closes the\n   * sheet. Half the lowest detent: the reader has visibly pulled most of it\n   * away, so settling back up would fight them.\n   */\n  var CLOSE_FRACTION = 0.5;\n\n  /**\n   * The detents a sheet declares, in rising order, with unknown names and\n   * duplicates dropped.\n   *\n   * @param {string|null|undefined} declared Comma-separated detent names, as\n   *   the template's `data-detents` attribute carries them.\n   * @returns {Array<string>} Never contains a name outside ORDER.\n   */\n  function parseDetents(declared) {\n    if (!declared) return [];\n    var names = String(declared)\n      .split(',')\n      .map(function (name) {\n        return name.trim();\n      });\n    return ORDER.filter(function (name) {\n      return names.indexOf(name) !== -1;\n    });\n  }\n\n  /**\n   * The detent a sheet opens at: the requested one when it is declared,\n   * otherwise the lowest declared detent.\n   *\n   * @param {Array<string>} detents Declared detents, rising.\n   * @param {string|null|undefined} requested\n   * @returns {string|null} Null when nothing is declared.\n   */\n  function initialDetent(detents, requested) {\n    if (!detents.length) return null;\n    if (requested && detents.indexOf(requested) !== -1) return requested;\n    return detents[0];\n  }\n\n  /**\n   * The detent the handle button moves to: the next one up, and back to the\n   * lowest from the top. A press on the handle is the keyboard and switch\n   * user's way through the three states, so it cycles rather than toggling\n   * between two.\n   *\n   * @param {Array<string>} detents Declared detents, rising.\n   * @param {string|null|undefined} current\n   * @returns {string|null} Null when nothing is declared.\n   */\n  function nextDetent(detents, current) {\n    if (!detents.length) return null;\n    var at = current ? detents.indexOf(current) : -1;\n    if (at === -1) return detents[0];\n    return detents[(at + 1) % detents.length];\n  }\n\n  /**\n   * Keep a dragged height between nothing and the full detent: the sheet\n   * may be pulled down to its close, but never up past the screen.\n   *\n   * @param {number} height The height the finger would make.\n   * @param {number} max The full detent's height (or the tallest declared).\n   * @returns {number}\n   */\n  function clampHeight(height, max) {\n    if (!Number.isFinite(height)) return 0;\n    if (height < 0) return 0;\n    if (Number.isFinite(max) && height > max) return max;\n    return height;\n  }\n\n  /**\n   * Where a released sheet settles.\n   *\n   * @param {{\n   *   detents: Array<string>,\n   *   heights: Record<string, number>,\n   *   height: number,\n   *   velocity?: number,\n   * }} release\n   *   `detents` \u2014 the declared detents, rising.\n   *   `heights` \u2014 each declared detent's height in px.\n   *   `height` \u2014 the sheet's height at release.\n   *   `velocity` \u2014 px/ms at release, positive while growing; 0 if unknown.\n   * @returns {string|null} A declared detent name, `'close'`, or null when\n   *   nothing is declared.\n   */\n  function settle(release) {\n    var detents = release.detents;\n    if (!detents.length) return null;\n    var velocity = Number.isFinite(release.velocity) ? Number(release.velocity) : 0;\n    var projected = release.height + velocity * PROJECTION_MS;\n\n    var lowest = release.heights[detents[0]];\n    if (projected < lowest * CLOSE_FRACTION) return 'close';\n\n    var best = detents[0];\n    var bestDistance = Infinity;\n    detents.forEach(function (name) {\n      var distance = Math.abs(release.heights[name] - projected);\n      if (distance < bestDistance) {\n        best = name;\n        bestDistance = distance;\n      }\n    });\n    return best;\n  }\n\n  window.pwaSheetDetentsCore = Object.freeze({\n    ORDER: ORDER,\n    PROJECTION_MS: PROJECTION_MS,\n    CLOSE_FRACTION: CLOSE_FRACTION,\n    parseDetents: parseDetents,\n    initialDetent: initialDetent,\n    nextDetent: nextDetent,\n    clampHeight: clampHeight,\n    settle: settle,\n  });\n})();\n"],
  "mappings": "CA6BC,UAAgC,CAC/B,aAGA,IAAIA,EAAQ,OAAO,OAAO,CAAC,OAAQ,OAAQ,MAAM,CAAC,EAO9CC,EAAgB,IAOhBC,EAAiB,GAUrB,SAASC,EAAaC,EAAU,CAC9B,GAAI,CAACA,EAAU,MAAO,CAAC,EACvB,IAAIC,EAAQ,OAAOD,CAAQ,EACxB,MAAM,GAAG,EACT,IAAI,SAAUE,EAAM,CACnB,OAAOA,EAAK,KAAK,CACnB,CAAC,EACH,OAAON,EAAM,OAAO,SAAUM,EAAM,CAClC,OAAOD,EAAM,QAAQC,CAAI,IAAM,EACjC,CAAC,CACH,CAUA,SAASC,EAAcC,EAASC,EAAW,CACzC,OAAKD,EAAQ,OACTC,GAAaD,EAAQ,QAAQC,CAAS,IAAM,GAAWA,EACpDD,EAAQ,CAAC,EAFY,IAG9B,CAYA,SAASE,EAAWF,EAASG,EAAS,CACpC,GAAI,CAACH,EAAQ,OAAQ,OAAO,KAC5B,IAAII,EAAKD,EAAUH,EAAQ,QAAQG,CAAO,EAAI,GAC9C,OAAIC,IAAO,GAAWJ,EAAQ,CAAC,EACxBA,GAASI,EAAK,GAAKJ,EAAQ,MAAM,CAC1C,CAUA,SAASK,EAAYC,EAAQC,EAAK,CAEhC,MADI,CAAC,OAAO,SAASD,CAAM,GACvBA,EAAS,EAAU,EACnB,OAAO,SAASC,CAAG,GAAKD,EAASC,EAAYA,EAC1CD,CACT,CAkBA,SAASE,EAAOC,EAAS,CACvB,IAAIT,EAAUS,EAAQ,QACtB,GAAI,CAACT,EAAQ,OAAQ,OAAO,KAC5B,IAAIU,EAAW,OAAO,SAASD,EAAQ,QAAQ,EAAI,OAAOA,EAAQ,QAAQ,EAAI,EAC1EE,EAAYF,EAAQ,OAASC,EAAWjB,EAExCmB,EAASH,EAAQ,QAAQT,EAAQ,CAAC,CAAC,EACvC,GAAIW,EAAYC,EAASlB,EAAgB,MAAO,QAEhD,IAAImB,EAAOb,EAAQ,CAAC,EAChBc,EAAe,IACnB,OAAAd,EAAQ,QAAQ,SAAUF,EAAM,CAC9B,IAAIiB,EAAW,KAAK,IAAIN,EAAQ,QAAQX,CAAI,EAAIa,CAAS,EACrDI,EAAWD,IACbD,EAAOf,EACPgB,EAAeC,EAEnB,CAAC,EACMF,CACT,CAEA,OAAO,oBAAsB,OAAO,OAAO,CACzC,MAAOrB,EACP,cAAeC,EACf,eAAgBC,EAChB,aAAcC,EACd,cAAeI,EACf,WAAYG,EACZ,YAAaG,EACb,OAAQG,CACV,CAAC,CACH,GAAG",
  "names": ["ORDER", "PROJECTION_MS", "CLOSE_FRACTION", "parseDetents", "declared", "names", "name", "initialDetent", "detents", "requested", "nextDetent", "current", "at", "clampHeight", "height", "max", "settle", "release", "velocity", "projected", "lowest", "best", "bestDistance", "distance"]
}
