{
  "version": 3,
  "sources": ["route_cursor_core.js"],
  "sourcesContent": ["/*\n * static/js/route_cursor_core.js \u2014 one cursor shared by the map, the rail\n * and the point card (SNOW-1016).\n *\n * The map and the rail are two drawings of the same route, and they are\n * only worth having together if a place on one is the same place on the\n * other (rail two was a third until SNOW-1065 removed it). That needs one\n * shared coordinate, and a DISTANCE is not it: the elevation profile's x-axis is summed distance along the\n * simplified geometry, while the sampler's segments are strides along its\n * own walk, and the two disagree by a chord per switchback\n * (apps/routes/services/slope_summary.py, \"two length sources\").\n *\n * THE COORDINATE IS THE SAMPLE INDEX \u2014 an integer into the `angles` array\n * every surface already holds on `properties.slope` (route_slope_core.js).\n * N + 1 boundary points bound N segments, so a route has indices 0 to\n * N - 1. Each surface converts its own geometry to an index and back in its\n * own code; nothing passes a distance or a fraction across this boundary,\n * because that conversion is exactly the one the surfaces disagree on.\n *\n * This module owns no DOM and reads no globals. It holds one thing \u2014 the\n * cursor `index` \u2014 and tells subscribers when it changes. It holds no\n * selection: SNOW-1052 took band and passage selection off the rails, and\n * leg selection went on 2026-10-02 (a point is the only thing a reader\n * places on a route now). An index is clamped to [0, N - 1].\n *\n * `null` is a real state for the index: no point placed. A\n * non-finite number is not \u2014 it is a conversion bug in the surface that\n * sent it, and it throws.\n *\n * Exports (frozen `self.pwaRouteCursorCore`):\n *\n *   createRouteCursor(count) \u2014 a cursor over `count` segments\n *\n * and the cursor it returns:\n *\n *   state()                 \u2014 the current frozen `{index}`\n *   setIndex(index | null)\n *   subscribe(fn)           \u2014 returns the unsubscribe function\n */\n\n// @ts-check\n\n(function () {\n  'use strict';\n\n  /**\n   * @typedef {{index: ?number}} CursorState\n   */\n\n  /**\n   * Clamp a number into a closed range.\n   *\n   * @param {number} value The number.\n   * @param {number} low The lowest value allowed.\n   * @param {number} high The highest value allowed.\n   * @returns {number}\n   */\n  function clamp(value, low, high) {\n    return Math.min(high, Math.max(low, value));\n  }\n\n  /**\n   * Turn a surface's index into an integer, or fail.\n   *\n   * A surface converting from pixels may hand over a fraction, which rounds\n   * to the nearest segment. NaN or Infinity means its conversion broke, and\n   * clamping it would put the cursor at one end of the route with no trace\n   * of why.\n   *\n   * @param {*} value The candidate index.\n   * @param {string} name What it is, for the error message.\n   * @returns {number}\n   */\n  function toIndex(value, name) {\n    if (typeof value !== 'number' || !isFinite(value)) {\n      throw new TypeError(`${name} must be a finite number, got ${value}`);\n    }\n    return Math.round(value);\n  }\n\n  /**\n   * Create a cursor over one route's segments.\n   *\n   * @param {number} count How many segments the route has \u2014 the length of\n   *   `properties.slope.angles`.\n   * @returns {{\n   *   state: function(): CursorState,\n   *   setIndex: function(?number): void,\n   *   subscribe: function(function(CursorState): void): function(): void,\n   * }}\n   */\n  function createRouteCursor(count) {\n    if (!Number.isInteger(count) || count < 1) {\n      throw new RangeError(`count must be a positive integer, got ${count}`);\n    }\n    const last = count - 1;\n\n    /** @type {CursorState} */\n    let current = Object.freeze({ index: null });\n    /** @type {Set<function(CursorState): void>} */\n    const subscribers = new Set();\n\n    /**\n     * Replace the state and tell subscribers \u2014 unless nothing changed.\n     *\n     * @param {?number} index The new cursor index.\n     */\n    function commit(index) {\n      if (index === current.index) return;\n      current = Object.freeze({ index: index });\n      subscribers.forEach((fn) => fn(current));\n    }\n\n    /**\n     * Move the cursor, or clear it with `null`.\n     *\n     * @param {?number} index The index a surface converted its pointer to.\n     */\n    function setIndex(index) {\n      commit(index === null ? null : clamp(toIndex(index, 'index'), 0, last));\n    }\n\n    /**\n     * Call `fn` with the new state after every change.\n     *\n     * Not called on subscription and not called for a set that changes\n     * nothing, so a surface redraws exactly once per real change.\n     *\n     * @param {function(CursorState): void} fn The redraw.\n     * @returns {function(): void} Removes the subscription.\n     */\n    function subscribe(fn) {\n      subscribers.add(fn);\n      return () => {\n        subscribers.delete(fn);\n      };\n    }\n\n    return Object.freeze({\n      state: () => current,\n      setIndex: setIndex,\n      subscribe: subscribe,\n    });\n  }\n\n  self.pwaRouteCursorCore = Object.freeze({\n    createRouteCursor: createRouteCursor,\n  });\n})();\n"],
  "mappings": "CA0CC,UAAY,CACX,aAcA,SAASA,EAAMC,EAAOC,EAAKC,EAAM,CAC/B,OAAO,KAAK,IAAIA,EAAM,KAAK,IAAID,EAAKD,CAAK,CAAC,CAC5C,CAcA,SAASG,EAAQH,EAAOI,EAAM,CAC5B,GAAI,OAAOJ,GAAU,UAAY,CAAC,SAASA,CAAK,EAC9C,MAAM,IAAI,UAAU,GAAGI,CAAI,iCAAiCJ,CAAK,EAAE,EAErE,OAAO,KAAK,MAAMA,CAAK,CACzB,CAaA,SAASK,EAAkBC,EAAO,CAChC,GAAI,CAAC,OAAO,UAAUA,CAAK,GAAKA,EAAQ,EACtC,MAAM,IAAI,WAAW,yCAAyCA,CAAK,EAAE,EAEvE,MAAMC,EAAOD,EAAQ,EAGrB,IAAIE,EAAU,OAAO,OAAO,CAAE,MAAO,IAAK,CAAC,EAE3C,MAAMC,EAAc,IAAI,IAOxB,SAASC,EAAOC,EAAO,CACjBA,IAAUH,EAAQ,QACtBA,EAAU,OAAO,OAAO,CAAE,MAAOG,CAAM,CAAC,EACxCF,EAAY,QAASG,GAAOA,EAAGJ,CAAO,CAAC,EACzC,CAOA,SAASK,EAASF,EAAO,CACvBD,EAAOC,IAAU,KAAO,KAAOZ,EAAMI,EAAQQ,EAAO,OAAO,EAAG,EAAGJ,CAAI,CAAC,CACxE,CAWA,SAASO,EAAUF,EAAI,CACrB,OAAAH,EAAY,IAAIG,CAAE,EACX,IAAM,CACXH,EAAY,OAAOG,CAAE,CACvB,CACF,CAEA,OAAO,OAAO,OAAO,CACnB,MAAO,IAAMJ,EACb,SAAUK,EACV,UAAWC,CACb,CAAC,CACH,CAEA,KAAK,mBAAqB,OAAO,OAAO,CACtC,kBAAmBT,CACrB,CAAC,CACH,GAAG",
  "names": ["clamp", "value", "low", "high", "toIndex", "name", "createRouteCursor", "count", "last", "current", "subscribers", "commit", "index", "fn", "setIndex", "subscribe"]
}
