function x(r, e, t, s) {
  const n = new Set(r.selected);
  let o = r.anchor;
  if (t === "single")
    n.clear(), n.add(e), o = e;
  else if (t === "multiple") {
    const i = s.ctrlKey || s.metaKey;
    if (s.shiftKey && r.anchor !== null) {
      const c = Math.min(r.anchor, e), a = Math.max(r.anchor, e);
      for (let d = c; d <= a; d++)
        n.add(d);
    } else i ? (n.has(e) ? n.delete(e) : n.add(e), o = e) : (n.clear(), n.add(e), o = e);
  }
  return { selected: n, lastSelected: e, anchor: o };
}
function _(r) {
  const e = /* @__PURE__ */ new Set();
  for (let t = 0; t < r; t++)
    e.add(t);
  return e;
}
function D(r, e) {
  const t = [], s = [];
  for (const n of e)
    r.has(n) || t.push(n);
  for (const n of r)
    e.has(n) || s.push(n);
  return { added: t, removed: s };
}
function b(r) {
  if (!r) return -1;
  const e = r.getAttribute("data-row");
  if (e) return parseInt(e, 10);
  const t = r.closest(".data-grid-row");
  if (!t) return -1;
  const s = t.parentElement;
  if (!s) return -1;
  const n = s.querySelectorAll(":scope > .data-grid-row");
  for (let o = 0; o < n.length; o++)
    if (n[o] === t) return o;
  return -1;
}
function w(r) {
  r && r.querySelectorAll(".cell-focus").forEach((e) => e.classList.remove("cell-focus"));
}
const p = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class y {
  /**
   * Plugin dependencies - declare other plugins this one requires.
   *
   * Dependencies are validated when the plugin is attached.
   * Required dependencies throw an error if missing.
   * Optional dependencies log an info message if missing.
   *
   * @example
   * ```typescript
   * static readonly dependencies: PluginDependency[] = [
   *   { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
   *   { name: 'selection', required: false, reason: 'Enables selection-based undo' },
   * ];
   * ```
   */
  static dependencies;
  /**
   * Plugin version - defaults to grid version for built-in plugins.
   * Third-party plugins can override with their own semver.
   */
  version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
  /** CSS styles to inject into the grid's shadow DOM */
  styles;
  /** Custom cell renderers keyed by type name */
  cellRenderers;
  /** Custom header renderers keyed by type name */
  headerRenderers;
  /** Custom cell editors keyed by type name */
  cellEditors;
  /** The grid instance this plugin is attached to */
  grid;
  /** Plugin configuration - merged with defaults in attach() */
  config;
  /** User-provided configuration from constructor */
  userConfig;
  /**
   * Plugin-level AbortController for event listener cleanup.
   * Created fresh in attach(), aborted in detach().
   * This ensures event listeners are properly cleaned up when plugins are re-attached.
   */
  #t;
  /**
   * Default configuration - subclasses should override this getter.
   * Note: This must be a getter (not property initializer) for proper inheritance
   * since property initializers run after parent constructor.
   */
  get defaultConfig() {
    return {};
  }
  constructor(e = {}) {
    this.userConfig = e;
  }
  /**
   * Called when the plugin is attached to a grid.
   * Override to set up event listeners, initialize state, etc.
   *
   * @example
   * ```ts
   * attach(grid: GridElement): void {
   *   super.attach(grid);
   *   // Set up document-level listeners with auto-cleanup
   *   document.addEventListener('keydown', this.handleEscape, {
   *     signal: this.disconnectSignal
   *   });
   * }
   * ```
   */
  attach(e) {
    this.#t?.abort(), this.#t = new AbortController(), this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
  }
  /**
   * Called when the plugin is detached from a grid.
   * Override to clean up event listeners, timers, etc.
   *
   * @example
   * ```ts
   * detach(): void {
   *   // Clean up any state not handled by disconnectSignal
   *   this.selectedRows.clear();
   *   this.cache = null;
   * }
   * ```
   */
  detach() {
    this.#t?.abort(), this.#t = void 0;
  }
  /**
   * Get another plugin instance from the same grid.
   * Use for inter-plugin communication.
   *
   * @example
   * ```ts
   * const selection = this.getPlugin(SelectionPlugin);
   * if (selection) {
   *   const selectedRows = selection.getSelectedRows();
   * }
   * ```
   */
  getPlugin(e) {
    return this.grid?.getPlugin(e);
  }
  /**
   * Emit a custom event from the grid.
   */
  emit(e, t) {
    this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
  }
  /**
   * Emit a cancelable custom event from the grid.
   * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
   */
  emitCancelable(e, t) {
    const s = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(s), s.defaultPrevented;
  }
  /**
   * Request a re-render of the grid.
   */
  requestRender() {
    this.grid?.requestRender?.();
  }
  /**
   * Request a re-render and restore focus styling afterward.
   * Use this when a plugin action (like expand/collapse) triggers a render
   * but needs to maintain keyboard navigation focus.
   */
  requestRenderWithFocus() {
    this.grid?.requestRenderWithFocus?.();
  }
  /**
   * Request a lightweight style update without rebuilding DOM.
   * Use this instead of requestRender() when only CSS classes need updating.
   */
  requestAfterRender() {
    this.grid?.requestAfterRender?.();
  }
  /**
   * Get the current rows from the grid.
   */
  get rows() {
    return this.grid?.rows ?? [];
  }
  /**
   * Get the original unfiltered/unprocessed rows from the grid.
   * Use this when you need all source data regardless of active filters.
   */
  get sourceRows() {
    return this.grid?.sourceRows ?? [];
  }
  /**
   * Get the current columns from the grid.
   */
  get columns() {
    return this.grid?.columns ?? [];
  }
  /**
   * Get only visible columns from the grid (excludes hidden).
   * Use this for rendering that needs to match the grid template.
   */
  get visibleColumns() {
    return this.grid?._visibleColumns ?? [];
  }
  /**
   * Get the grid as an HTMLElement for direct DOM operations.
   * Use sparingly - prefer the typed GridElementRef API when possible.
   *
   * @example
   * ```ts
   * const width = this.gridElement.clientWidth;
   * this.gridElement.classList.add('my-plugin-active');
   * ```
   */
  get gridElement() {
    return this.grid;
  }
  /**
   * Get the render root of the grid for DOM queries.
   * @deprecated Use `gridElement` instead. This getter exists only for backward compatibility.
   *
   * With Shadow DOM removed, the grid element itself is the render root.
   * All new code should use `this.gridElement` for DOM queries.
   *
   * @example
   * // OLD (deprecated)
   * const rows = this.shadowRoot?.querySelector('.rows');
   *
   * // NEW (preferred)
   * const rows = this.gridElement.querySelector('.rows');
   */
  get shadowRoot() {
    return this.gridElement;
  }
  /**
   * Get the disconnect signal for event listener cleanup.
   * This signal is aborted when the grid disconnects from the DOM.
   * Use this when adding event listeners that should be cleaned up automatically.
   *
   * Best for:
   * - Document/window-level listeners added in attach()
   * - Listeners on the grid element itself
   * - Any listener that should persist across renders
   *
   * Not needed for:
   * - Listeners on elements created in afterRender() (removed with element)
   *
   * @example
   * element.addEventListener('click', handler, { signal: this.disconnectSignal });
   * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
   */
  get disconnectSignal() {
    return this.#t?.signal ?? this.grid?.disconnectSignal;
  }
  /**
   * Get the grid-level icons configuration.
   * Returns merged icons (user config + defaults).
   */
  get gridIcons() {
    const e = this.grid?.gridConfig?.icons ?? {};
    return { ...p, ...e };
  }
  // #region Animation Helpers
  /**
   * Check if animations are enabled at the grid level.
   * Respects gridConfig.animation.mode and the CSS variable set by the grid.
   *
   * Plugins should use this to skip animations when:
   * - Animation mode is 'off' or `false`
   * - User prefers reduced motion and mode is 'reduced-motion' (default)
   *
   * @example
   * ```ts
   * private get animationStyle(): 'slide' | 'fade' | false {
   *   if (!this.isAnimationEnabled) return false;
   *   return this.config.animation ?? 'slide';
   * }
   * ```
   */
  get isAnimationEnabled() {
    const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
    if (e === !1 || e === "off") return !1;
    if (e === !0 || e === "on") return !0;
    const t = this.gridElement;
    return t ? getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
  }
  /**
   * Get the animation duration in milliseconds from CSS variable.
   * Falls back to 200ms if not set.
   *
   * Plugins can use this for their animation timing to stay consistent
   * with the grid-level animation.duration setting.
   *
   * @example
   * ```ts
   * element.animate(keyframes, { duration: this.animationDuration });
   * ```
   */
  get animationDuration() {
    const e = this.gridElement;
    if (e) {
      const t = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), s = parseInt(t, 10);
      if (!isNaN(s)) return s;
    }
    return 200;
  }
  // #endregion
  /**
   * Resolve an icon value to string or HTMLElement.
   * Checks plugin config first, then grid-level icons, then defaults.
   *
   * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
   * @param pluginOverride - Optional plugin-level override
   * @returns The resolved icon value
   */
  resolveIcon(e, t) {
    return t !== void 0 ? t : this.gridIcons[e];
  }
  /**
   * Set an icon value on an element.
   * Handles both string (text/HTML) and HTMLElement values.
   *
   * @param element - The element to set the icon on
   * @param icon - The icon value (string or HTMLElement)
   */
  setIcon(e, t) {
    typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
  }
  /**
   * Log a warning message.
   */
  warn(e) {
    console.warn(`[tbw-grid:${this.name}] ${e}`);
  }
  // #endregion
}
function h(r) {
  return r.meta?.utility === !0;
}
function g(r) {
  return {
    startRow: Math.min(r.startRow, r.endRow),
    startCol: Math.min(r.startCol, r.endCol),
    endRow: Math.max(r.startRow, r.endRow),
    endCol: Math.max(r.startCol, r.endCol)
  };
}
function A(r) {
  const e = g(r);
  return {
    from: { row: e.startRow, col: e.startCol },
    to: { row: e.endRow, col: e.endCol }
  };
}
function R(r) {
  return r.map(A);
}
function v(r, e, t) {
  const s = g(t);
  return r >= s.startRow && r <= s.endRow && e >= s.startCol && e <= s.endCol;
}
function m(r, e, t) {
  return t.some((s) => v(r, e, s));
}
function S(r) {
  const e = [], t = g(r);
  for (let s = t.startRow; s <= t.endRow; s++)
    for (let n = t.startCol; n <= t.endCol; n++)
      e.push({ row: s, col: n });
  return e;
}
function E(r) {
  const e = /* @__PURE__ */ new Map();
  for (const t of r)
    for (const s of S(t))
      e.set(`${s.row},${s.col}`, s);
  return [...e.values()];
}
function u(r, e) {
  return {
    startRow: r.row,
    startCol: r.col,
    endRow: e.row,
    endCol: e.col
  };
}
const I = "@layer tbw-plugins{tbw-grid.selecting .data-grid-row>.cell{-webkit-user-select:none;user-select:none}tbw-grid[data-has-focus] .data-grid-row.row-focus{background-color:var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%))}tbw-grid[data-selection-mode=row] .cell-focus{outline:none}tbw-grid .data-grid-row>.cell.selected{background-color:var(--tbw-range-selection-bg)}tbw-grid .data-grid-row>.cell.selected.top{border-top:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.bottom{border-bottom:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.first{border-left:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.last{border-right:2px solid var(--tbw-range-border-color)}tbw-grid .tbw-selection-summary{font-size:var(--tbw-font-size-sm, .8125rem);color:var(--tbw-color-fg-muted);white-space:nowrap}}";
function K(r, e, t) {
  if (r === "cell" && e.selectedCell)
    return {
      mode: r,
      ranges: [
        {
          from: { row: e.selectedCell.row, col: e.selectedCell.col },
          to: { row: e.selectedCell.row, col: e.selectedCell.col }
        }
      ]
    };
  if (r === "row" && e.selected.size > 0) {
    const s = [...e.selected].map((n) => ({
      from: { row: n, col: 0 },
      to: { row: n, col: t - 1 }
    }));
    return { mode: r, ranges: s };
  }
  return r === "range" && e.ranges.length > 0 ? { mode: r, ranges: R(e.ranges) } : { mode: r, ranges: [] };
}
class M extends y {
  name = "selection";
  styles = I;
  get defaultConfig() {
    return {
      mode: "cell"
    };
  }
  // #region Internal State
  /** Row selection state (row mode) */
  selected = /* @__PURE__ */ new Set();
  lastSelected = null;
  anchor = null;
  /** Range selection state (range mode) */
  ranges = [];
  activeRange = null;
  cellAnchor = null;
  isDragging = !1;
  /** Pending keyboard navigation update (processed in afterRender) */
  pendingKeyboardUpdate = null;
  /** Cell selection state (cell mode) */
  selectedCell = null;
  // #endregion
  // #region Lifecycle
  detach() {
    this.selected.clear(), this.ranges = [], this.activeRange = null, this.cellAnchor = null, this.isDragging = !1, this.selectedCell = null, this.pendingKeyboardUpdate = null;
  }
  // #endregion
  // #region Event Handlers
  onCellClick(e) {
    const { rowIndex: t, colIndex: s, originalEvent: n } = e, { mode: o } = this.config, i = this.columns[s], c = i && h(i);
    if (o === "cell")
      return c || (this.selectedCell = { row: t, col: s }, this.emit("selection-change", this.#e()), this.requestAfterRender()), !1;
    if (o === "row")
      return this.selected.clear(), this.selected.add(t), this.lastSelected = t, this.emit("selection-change", this.#e()), this.requestAfterRender(), !1;
    if (o === "range") {
      if (c)
        return !1;
      const a = n.shiftKey, d = n.ctrlKey || n.metaKey;
      if (a && this.cellAnchor) {
        const l = u(this.cellAnchor, { row: t, col: s });
        d ? this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] = l : this.ranges.push(l) : this.ranges = [l], this.activeRange = l;
      } else if (d) {
        const l = {
          startRow: t,
          startCol: s,
          endRow: t,
          endCol: s
        };
        this.ranges.push(l), this.activeRange = l, this.cellAnchor = { row: t, col: s };
      } else {
        const l = {
          startRow: t,
          startCol: s,
          endRow: t,
          endCol: s
        };
        this.ranges = [l], this.activeRange = l, this.cellAnchor = { row: t, col: s };
      }
      return this.emit("selection-change", this.#e()), this.requestAfterRender(), !1;
    }
    return !1;
  }
  onKeyDown(e) {
    const { mode: t } = this.config, n = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "PageUp", "PageDown"].includes(e.key);
    if (e.key === "Escape")
      return t === "cell" ? this.selectedCell = null : t === "row" ? (this.selected.clear(), this.anchor = null) : t === "range" && (this.ranges = [], this.activeRange = null, this.cellAnchor = null), this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
    if (t === "cell" && n)
      return queueMicrotask(() => {
        this.selectedCell = { row: this.grid._focusRow, col: this.grid._focusCol }, this.emit("selection-change", this.#e()), this.requestAfterRender();
      }), !1;
    if (t === "row" && (e.key === "ArrowUp" || e.key === "ArrowDown"))
      return queueMicrotask(() => {
        this.selected.clear(), this.selected.add(this.grid._focusRow), this.lastSelected = this.grid._focusRow, this.emit("selection-change", this.#e()), this.requestAfterRender();
      }), !1;
    if (t === "range" && n) {
      const o = e.key === "Tab", i = e.shiftKey && !o;
      return i && !this.cellAnchor && (this.cellAnchor = { row: this.grid._focusRow, col: this.grid._focusCol }), this.pendingKeyboardUpdate = { shiftKey: i }, queueMicrotask(() => this.requestAfterRender()), !1;
    }
    if (t === "range" && e.key === "a" && (e.ctrlKey || e.metaKey)) {
      const o = this.rows.length, i = this.columns.length;
      if (o > 0 && i > 0) {
        const c = {
          startRow: 0,
          startCol: 0,
          endRow: o - 1,
          endCol: i - 1
        };
        return this.ranges = [c], this.activeRange = c, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
      }
    }
    return !1;
  }
  onCellMouseDown(e) {
    if (this.config.mode !== "range" || e.rowIndex === void 0 || e.colIndex === void 0 || e.rowIndex < 0) return;
    const t = this.columns[e.colIndex];
    if (t && h(t) || e.originalEvent.shiftKey && this.cellAnchor)
      return;
    this.isDragging = !0;
    const s = e.rowIndex, n = e.colIndex;
    this.cellAnchor = { row: s, col: n }, e.originalEvent.ctrlKey || e.originalEvent.metaKey || (this.ranges = []);
    const i = {
      startRow: s,
      startCol: n,
      endRow: s,
      endCol: n
    };
    return this.ranges.push(i), this.activeRange = i, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
  }
  onCellMouseMove(e) {
    if (this.config.mode !== "range" || !this.isDragging || !this.cellAnchor || e.rowIndex === void 0 || e.colIndex === void 0 || e.rowIndex < 0) return;
    let t = e.colIndex;
    const s = this.columns[t];
    if (s && h(s)) {
      const o = this.columns.findIndex((i) => !h(i));
      o >= 0 && (t = o);
    }
    const n = u(this.cellAnchor, { row: e.rowIndex, col: t });
    return this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] = n : this.ranges.push(n), this.activeRange = n, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
  }
  onCellMouseUp(e) {
    if (this.config.mode === "range" && this.isDragging)
      return this.isDragging = !1, !0;
  }
  /**
   * Apply selection classes to visible cells/rows.
   * Shared by afterRender and onScrollRender.
   */
  #t() {
    const e = this.gridElement;
    if (!e) return;
    const { mode: t } = this.config;
    e.querySelectorAll(".cell").forEach((o) => {
      o.classList.remove("selected", "top", "bottom", "first", "last");
    });
    const n = e.querySelectorAll(".data-grid-row");
    if (n.forEach((o) => {
      o.classList.remove("selected", "row-focus");
    }), t === "row" && (w(e), n.forEach((o) => {
      const i = o.querySelector(".cell[data-row]"), c = b(i);
      c >= 0 && this.selected.has(c) && o.classList.add("selected", "row-focus");
    })), t === "range" && this.ranges.length > 0) {
      w(e);
      const o = this.activeRange ? g(this.activeRange) : null, i = this.columns.findIndex((a) => !h(a));
      this.columns.length - 1, e.querySelectorAll(".cell[data-row][data-col]").forEach((a) => {
        const d = parseInt(a.getAttribute("data-row") ?? "-1", 10), l = parseInt(a.getAttribute("data-col") ?? "-1", 10);
        if (d >= 0 && l >= 0) {
          const f = this.columns[l];
          if (f && h(f))
            return;
          if (m(d, l, this.ranges) && (a.classList.add("selected"), o)) {
            d === o.startRow && a.classList.add("top"), d === o.endRow && a.classList.add("bottom");
            const C = Math.max(o.startCol, i);
            l === C && a.classList.add("first"), l === o.endCol && a.classList.add("last");
          }
        }
      });
    }
  }
  afterRender() {
    const e = this.gridElement;
    if (!e) return;
    const t = e.children[0], { mode: s } = this.config;
    if (this.pendingKeyboardUpdate && s === "range") {
      const { shiftKey: n } = this.pendingKeyboardUpdate;
      this.pendingKeyboardUpdate = null;
      const o = this.grid._focusRow, i = this.grid._focusCol;
      if (n && this.cellAnchor) {
        const c = u(this.cellAnchor, { row: o, col: i });
        this.ranges = [c], this.activeRange = c;
      } else n || (this.ranges = [], this.activeRange = null, this.cellAnchor = { row: o, col: i });
      this.emit("selection-change", this.#e());
    }
    this.grid.setAttribute("data-selection-mode", s), t && t.classList.toggle("selecting", this.isDragging), this.#t();
  }
  /**
   * Called after scroll-triggered row rendering.
   * Reapplies selection classes to recycled DOM elements.
   */
  onScrollRender() {
    this.#t();
  }
  // #endregion
  // #region Public API
  /**
   * Get the current selection as a unified result.
   * Works for all selection modes and always returns ranges.
   *
   * @example
   * ```ts
   * const selection = plugin.getSelection();
   * if (selection.ranges.length > 0) {
   *   const { from, to } = selection.ranges[0];
   *   // For cell mode: from === to (single cell)
   *   // For row mode: from.col = 0, to.col = lastCol (full row)
   *   // For range mode: rectangular selection
   * }
   * ```
   */
  getSelection() {
    return {
      mode: this.config.mode,
      ranges: this.#e().ranges,
      anchor: this.cellAnchor
    };
  }
  /**
   * Get the selected cell (cell mode only).
   * @deprecated Use `getSelection()` instead for a unified API across all modes.
   */
  getSelectedCell() {
    const { mode: e, ranges: t } = this.getSelection();
    return e === "cell" && t.length > 0 ? t[0].from : null;
  }
  /**
   * Get all selected row indices (row mode).
   * @deprecated Use `getSelection().ranges` instead - each range represents a full row.
   */
  getSelectedRows() {
    const { mode: e, ranges: t } = this.getSelection();
    return e === "row" ? t.map((s) => s.from.row) : [];
  }
  /**
   * Get all selected cell ranges in public format.
   * @deprecated Use `getSelection().ranges` instead.
   */
  getRanges() {
    return this.getSelection().ranges;
  }
  /**
   * Get all selected cells across all ranges.
   */
  getSelectedCells() {
    return E(this.ranges);
  }
  /**
   * Check if a specific cell is in range selection.
   */
  isCellSelected(e, t) {
    return m(e, t, this.ranges);
  }
  /**
   * Clear all selection.
   */
  clearSelection() {
    this.selectedCell = null, this.selected.clear(), this.anchor = null, this.ranges = [], this.activeRange = null, this.cellAnchor = null, this.emit("selection-change", { mode: this.config.mode, ranges: [] }), this.requestAfterRender();
  }
  /**
   * Set selected ranges programmatically.
   */
  setRanges(e) {
    this.ranges = e.map((t) => ({
      startRow: t.from.row,
      startCol: t.from.col,
      endRow: t.to.row,
      endCol: t.to.col
    })), this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null, this.emit("selection-change", {
      mode: this.config.mode,
      ranges: R(this.ranges)
    }), this.requestAfterRender();
  }
  // #endregion
  // #region Private Helpers
  #e() {
    return K(
      this.config.mode,
      {
        selectedCell: this.selectedCell,
        selected: this.selected,
        ranges: this.ranges
      },
      this.columns.length
    );
  }
  // #endregion
}
export {
  M as SelectionPlugin,
  D as computeSelectionDiff,
  x as handleRowClick,
  _ as selectAll
};
//# sourceMappingURL=index.js.map
