{
  "version": 3,
  "sources": ["sheet_playback_core.js"],
  "sourcesContent": ["// @ts-check\n/*\n * static/js/sheet_playback_core.js \u2014 what an object sheet does while the\n * timeline plays (SNOW-1239).\n *\n * The timelapse announces a `snowdesk:date-changed` every 200 ms frame. The\n * weather, place and region sheets each reloaded on every one, so a sheet\n * left open during playback fired a server-rendered request per frame and\n * cancelled none of them. Now the sheet PAUSES: it stays open, says\n * \"Playback in progress\" in a band over its dimmed body, asks for nothing,\n * and loads once \u2014 for the day playback stopped on \u2014 when it stops.\n *\n * The three sheet modules (map_weather_detail.js, map_place_sheet.js,\n * map_region_sheet.js) share these pieces rather than each writing them:\n *\n *   - ``createGuard`` \u2014 one request in flight per sheet. ``begin`` aborts\n *     the previous request and hands out a signal for the next;\n *     ``invalidate`` aborts it without starting another (a close, a pause),\n *     so a late answer can never paint.\n *   - ``shouldReload`` \u2014 a change of day reloads the sheet unless it is a\n *     timelapse frame during playback.\n *   - ``isPlaying`` \u2014 the map state's playback flag, false when the map\n *     state is absent.\n *   - ``isAbort`` \u2014 whether a rejection is the guard's own abort, which a\n *     sheet treats as silence rather than \"couldn't be loaded\".\n *   - ``markPaused`` \u2014 the band and the dim. Both live ON the sheet element\n *     (``data-sheet-paused`` and one band child), not on a wrapper, because\n *     every sheet paints by replacing its children and a wrapper would go\n *     with them; a module re-applies it after any paint made while paused.\n *     The dim is one data-attribute rule in static/css/map.css.\n *\n * The band is cloned from ``#sheet-playback-band-template`` (map.html), so\n * its copy is translated; the English fallback is for a page that somehow\n * renders without it. When the sheet holds no other ``data-sheet-peek``\n * element \u2014 a sheet first opened during playback \u2014 the band is the peek,\n * so map_sheet.js measures a real row rather than its fallback height.\n */\n(function () {\n  'use strict';\n\n  const TEMPLATE_ID = 'sheet-playback-band-template';\n  const BAND_ATTR = 'data-sheet-playback-band';\n  const PEEK_ATTR = 'data-sheet-peek';\n\n  /**\n   * One request in flight at a time, cancellable.\n   *\n   * Tolerates a runtime with no ``AbortController``: the token alone then\n   * keeps a late answer from painting, and the request simply runs out.\n   *\n   * @returns {{\n   *   begin: function(): {signal: (AbortSignal|undefined), isCurrent: function(): boolean},\n   *   invalidate: function(): void,\n   * }}\n   */\n  function createGuard() {\n    let token = 0;\n    /** @type {?AbortController} */\n    let controller = null;\n\n    /** Abort the request in flight, if there is one. */\n    function abort() {\n      if (controller) controller.abort();\n      controller = null;\n    }\n\n    return {\n      /**\n       * Start a request, superseding the one before it.\n       *\n       * @returns {{signal: (AbortSignal|undefined), isCurrent: function(): boolean}}\n       */\n      begin() {\n        abort();\n        token += 1;\n        const mine = token;\n        controller = typeof AbortController === 'function' ? new AbortController() : null;\n        return {\n          signal: controller ? controller.signal : undefined,\n          isCurrent: () => mine === token,\n        };\n      },\n      /** Abort the request in flight and start none. */\n      invalidate() {\n        token += 1;\n        abort();\n      },\n    };\n  }\n\n  /**\n   * Whether a change of day should reload an open sheet.\n   *\n   * @param {unknown} source The event's ``detail.source``.\n   * @param {boolean} playing Whether the timeline is playing.\n   * @returns {boolean} False only for a timelapse frame during playback.\n   */\n  function shouldReload(source, playing) {\n    return !(source === 'timelapse' && playing);\n  }\n\n  /**\n   * Whether the timeline is playing.\n   *\n   * @returns {boolean}\n   */\n  function isPlaying() {\n    return window.snowdeskMapState?.isPlaying === true;\n  }\n\n  /**\n   * Whether a rejection is an abort the guard asked for.\n   *\n   * @param {unknown} err\n   * @returns {boolean}\n   */\n  function isAbort(err) {\n    return !!err && /** @type {{name?: unknown}} */ (err).name === 'AbortError';\n  }\n\n  /**\n   * A fresh band: the template's, or a plain line when the page has none.\n   *\n   * @returns {HTMLElement}\n   */\n  function buildBand() {\n    const template = /** @type {HTMLTemplateElement|null} */ (document.getElementById(TEMPLATE_ID));\n    const source = template && template.content ? template.content.firstElementChild : null;\n    if (source) {\n      const band = /** @type {HTMLElement} */ (source.cloneNode(true));\n      band.setAttribute(BAND_ATTR, '');\n      return band;\n    }\n    const strings = window.pwaStrings\n      ? window.pwaStrings.read(TEMPLATE_ID, { band: 'Playback in progress' })\n      : { band: 'Playback in progress' };\n    const band = document.createElement('p');\n    band.setAttribute(BAND_ATTR, '');\n    band.className = 'px-2 py-2 text-xs font-mono uppercase tracking-widest text-text-3';\n    band.textContent = strings.band;\n    return band;\n  }\n\n  /**\n   * Show or clear the paused state on one sheet.\n   *\n   * Idempotent, so a module calls it after every paint and on every frame:\n   * an existing band is kept (only its peek mark follows the content), so\n   * playback does not churn the sheet's children 5 times a second.\n   *\n   * @param {?HTMLElement} sheetEl The sheet container.\n   * @param {boolean} on Whether the sheet is paused.\n   * @returns {void}\n   */\n  function markPaused(sheetEl, on) {\n    if (!sheetEl) return;\n    const existing = sheetEl.querySelector(':scope > [' + BAND_ATTR + ']');\n    if (!on) {\n      if (existing) existing.remove();\n      sheetEl.removeAttribute('data-sheet-paused');\n      sheetEl.removeAttribute('aria-busy');\n      return;\n    }\n    sheetEl.setAttribute('data-sheet-paused', '');\n    sheetEl.setAttribute('aria-busy', 'true');\n    const band = existing || buildBand();\n    // The band is the peek only when nothing else is: a body's own peek\n    // row stays what the sheet rests on.\n    band.removeAttribute(PEEK_ATTR);\n    if (!sheetEl.querySelector('[' + PEEK_ATTR + ']')) band.setAttribute(PEEK_ATTR, '');\n    if (existing) return;\n    // After the drag handle, which map_sheet.js keeps as the first child.\n    const handle = sheetEl.querySelector(':scope > [data-sheet-handle]');\n    sheetEl.insertBefore(band, handle ? handle.nextSibling : sheetEl.firstChild);\n  }\n\n  window.pwaSheetPlayback = Object.freeze({\n    createGuard: createGuard,\n    shouldReload: shouldReload,\n    isPlaying: isPlaying,\n    isAbort: isAbort,\n    markPaused: markPaused,\n  });\n})();\n"],
  "mappings": "CAqCC,UAAY,CACX,aAEA,MAAMA,EAAc,+BACdC,EAAY,2BACZC,EAAY,kBAalB,SAASC,GAAc,CACrB,IAAIC,EAAQ,EAERC,EAAa,KAGjB,SAASC,GAAQ,CACXD,GAAYA,EAAW,MAAM,EACjCA,EAAa,IACf,CAEA,MAAO,CAML,OAAQ,CACNC,EAAM,EACNF,GAAS,EACT,MAAMG,EAAOH,EACb,OAAAC,EAAa,OAAO,iBAAoB,WAAa,IAAI,gBAAoB,KACtE,CACL,OAAQA,EAAaA,EAAW,OAAS,OACzC,UAAW,IAAME,IAASH,CAC5B,CACF,EAEA,YAAa,CACXA,GAAS,EACTE,EAAM,CACR,CACF,CACF,CASA,SAASE,EAAaC,EAAQC,EAAS,CACrC,MAAO,EAAED,IAAW,aAAeC,EACrC,CAOA,SAASC,GAAY,CACnB,OAAO,OAAO,kBAAkB,YAAc,EAChD,CAQA,SAASC,EAAQC,EAAK,CACpB,MAAO,CAAC,CAACA,GAAwCA,EAAK,OAAS,YACjE,CAOA,SAASC,GAAY,CACnB,MAAMC,EAAoD,SAAS,eAAef,CAAW,EACvFS,EAASM,GAAYA,EAAS,QAAUA,EAAS,QAAQ,kBAAoB,KACnF,GAAIN,EAAQ,CACV,MAAMO,EAAmCP,EAAO,UAAU,EAAI,EAC9D,OAAAO,EAAK,aAAaf,EAAW,EAAE,EACxBe,CACT,CACA,MAAMC,EAAU,OAAO,WACnB,OAAO,WAAW,KAAKjB,EAAa,CAAE,KAAM,sBAAuB,CAAC,EACpE,CAAE,KAAM,sBAAuB,EAC7BgB,EAAO,SAAS,cAAc,GAAG,EACvC,OAAAA,EAAK,aAAaf,EAAW,EAAE,EAC/Be,EAAK,UAAY,oEACjBA,EAAK,YAAcC,EAAQ,KACpBD,CACT,CAaA,SAASE,EAAWC,EAASC,EAAI,CAC/B,GAAI,CAACD,EAAS,OACd,MAAME,EAAWF,EAAQ,cAAc,aAAelB,EAAY,GAAG,EACrE,GAAI,CAACmB,EAAI,CACHC,GAAUA,EAAS,OAAO,EAC9BF,EAAQ,gBAAgB,mBAAmB,EAC3CA,EAAQ,gBAAgB,WAAW,EACnC,MACF,CACAA,EAAQ,aAAa,oBAAqB,EAAE,EAC5CA,EAAQ,aAAa,YAAa,MAAM,EACxC,MAAMH,EAAOK,GAAYP,EAAU,EAKnC,GAFAE,EAAK,gBAAgBd,CAAS,EACzBiB,EAAQ,cAAc,IAAMjB,EAAY,GAAG,GAAGc,EAAK,aAAad,EAAW,EAAE,EAC9EmB,EAAU,OAEd,MAAMC,EAASH,EAAQ,cAAc,8BAA8B,EACnEA,EAAQ,aAAaH,EAAMM,EAASA,EAAO,YAAcH,EAAQ,UAAU,CAC7E,CAEA,OAAO,iBAAmB,OAAO,OAAO,CACtC,YAAahB,EACb,aAAcK,EACd,UAAWG,EACX,QAASC,EACT,WAAYM,CACd,CAAC,CACH,GAAG",
  "names": ["TEMPLATE_ID", "BAND_ATTR", "PEEK_ATTR", "createGuard", "token", "controller", "abort", "mine", "shouldReload", "source", "playing", "isPlaying", "isAbort", "err", "buildBand", "template", "band", "strings", "markPaused", "sheetEl", "on", "existing", "handle"]
}
