UNPKG

apexcharts

Version:

A JavaScript Chart Library

2,787 lines 104 kB
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
  for (var prop in b || (b = {}))
    if (__hasOwnProp.call(b, prop))
      __defNormalProp(a, prop, b[prop]);
  if (__getOwnPropSymbols)
    for (var prop of __getOwnPropSymbols(b)) {
      if (__propIsEnum.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    }
  return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
/*!
 * ApexCharts v7.0.0
 * (c) 2018-2026 ApexCharts
 */
import * as _core from "apexcharts/core";
import _core__default from "apexcharts/core";
import { default as default2 } from "apexcharts/core";
const Graphics = _core.__apex_Graphics;
const AxesUtils = _core.__apex_axes_AxesUtils;
const Data = _core.__apex_Data;
const Series = _core.__apex_Series;
const Utils = _core.__apex_Utils;
const Environment = _core.__apex_Environment_Environment;
const BrowserAPIs = _core.__apex_BrowserAPIs_BrowserAPIs;
const SVGNS = _core.__apex_math_SVGNS;
class Exports {
  /**
   * @param {import('../types/internal').ChartStateW} w
   * @param {import('../types/internal').ChartContext} ctx
   */
  constructor(w, ctx) {
    this.w = w;
    this.ctx = ctx;
  }
  /**
   * @param {string} svgString
   */
  svgStringToNode(svgString) {
    const parser = new DOMParser();
    const svgDoc = parser.parseFromString(svgString, "image/svg+xml");
    return svgDoc.documentElement;
  }
  /**
   * @param {any} svg
   * @param {number} scale
   */
  scaleSvgNode(svg, scale) {
    const svgWidth = parseFloat(svg.getAttributeNS(null, "width"));
    const svgHeight = parseFloat(svg.getAttributeNS(null, "height"));
    svg.setAttributeNS(null, "width", svgWidth * scale);
    svg.setAttributeNS(null, "height", svgHeight * scale);
    svg.setAttributeNS(null, "viewBox", "0 0 " + svgWidth + " " + svgHeight);
  }
  /**
   * Inline any Strata canvas series layer into the clone as an SVG `<image>`.
   * A serialized `<canvas>` loses its bitmap, so a canvas-mode export would drop
   * the series; an `<image>` carrying the canvas `toDataURL()` preserves it in
   * place. Because it replaces the `<foreignObject>` at the same DOM position,
   * the grid-behind / annotations-in-front z-order is retained automatically.
   * No-op in SVG mode (no series canvas present).
   * @param {any} clonedNode the cloned elWrap about to be serialized
   */
  inlineCanvasLayers(clonedNode) {
    const w = this.w;
    const XLINK = "http://www.w3.org/1999/xlink";
    const origCanvases = w.dom.elWrap.querySelectorAll(
      ".apexcharts-series-canvas"
    );
    if (!origCanvases.length) return;
    const clonedFOs = clonedNode.querySelectorAll(".apexcharts-canvas-series");
    for (let i = 0; i < origCanvases.length && i < clonedFOs.length; i++) {
      let dataURL;
      try {
        dataURL = /** @type {HTMLCanvasElement} */
        origCanvases[i].toDataURL();
      } catch (e) {
        continue;
      }
      const fo = clonedFOs[i];
      const img = document.createElementNS(SVGNS, "image");
      img.setAttribute("x", fo.getAttribute("x") || "0");
      img.setAttribute("y", fo.getAttribute("y") || "0");
      img.setAttribute("width", fo.getAttribute("width") || "0");
      img.setAttribute("height", fo.getAttribute("height") || "0");
      img.setAttribute("href", dataURL);
      img.setAttributeNS(XLINK, "xlink:href", dataURL);
      if (fo.parentNode) fo.parentNode.replaceChild(img, fo);
    }
  }
  /**
   * `querySelectorAll` as a typed array. Both HTML and SVG elements carry
   * `style` / `classList`, but `NodeListOf<Element>` does not.
   * @param {ParentNode} root
   * @param {string} selector
   * @returns {Array<HTMLElement | SVGElement>}
   */
  queryStyleable(root, selector) {
    return (
      /** @type {Array<HTMLElement | SVGElement>} */
      Array.prototype.slice.call(root.querySelectorAll(selector))
    );
  }
  /**
   * Applies `styles` only where the element has no inline value for that
   * property yet.
   *
   * The rules being re-applied here came from a stylesheet, so anything a
   * module set inline has to keep winning exactly like it does in the live
   * DOM: `legend.fontSize` (Legend.js) and the heatmap gradient legend's
   * deliberate `display`/`overflow`/`padding` overrides on the legend wrap
   * (HeatmapGradientLegend.js) would otherwise be clobbered in the export.
   * @param {HTMLElement | SVGElement} el
   * @param {Record<string, string>} styles
   */
  setStyleDefaults(el, styles) {
    Object.keys(styles).forEach((prop) => {
      if (el.style.getPropertyValue(prop) === "") {
        el.style.setProperty(prop, styles[prop]);
      }
    });
  }
  /**
   * Re-applies, as inline styles on the clone, the rules that used to reach
   * the exported SVG through an injected `<style>` block. A strict
   * Content-Security-Policy without `'unsafe-inline'` blocks that block and
   * breaks the export, so the export must not depend on one. See #5146.
   *
   * Mirrors `src/assets/apexcharts-legend.css`. Interaction-only rules
   * (`cursor`, `pointer-events`) are carried over for parity even though they
   * do nothing in a static image; the layout rules are what matter.
   * @param {HTMLElement} clonedNode the cloned elWrap about to be serialized
   */
  applyExportStyles(clonedNode) {
    const w = this.w;
    this.queryStyleable(clonedNode, "style").forEach((el) => el.remove());
    this.queryStyleable(
      clonedNode,
      [
        ".apexcharts-tooltip",
        ".apexcharts-toolbar",
        ".apexcharts-xaxistooltip",
        ".apexcharts-yaxistooltip",
        ".apexcharts-xcrosshairs",
        ".apexcharts-ycrosshairs",
        ".apexcharts-zoom-rect",
        ".apexcharts-selection-rect"
      ].join(", ")
    ).forEach((el) => {
      el.style.setProperty("display", "none", "important");
    });
    this.queryStyleable(clonedNode, ".apexcharts-flip-y").forEach((el) => {
      this.setStyleDefaults(el, {
        transform: "scaleY(-1) translateY(-100%)",
        "transform-origin": "top",
        "transform-box": "fill-box"
      });
    });
    this.queryStyleable(clonedNode, ".apexcharts-flip-x").forEach((el) => {
      this.setStyleDefaults(el, {
        transform: "scaleX(-1)",
        "transform-origin": "center",
        "transform-box": "fill-box"
      });
    });
    if (!w.config.legend.show || !w.dom.elLegendWrap || !w.dom.elLegendWrap.children.length) {
      return;
    }
    this.queryStyleable(clonedNode, ".apexcharts-legend").forEach((el) => {
      this.setStyleDefaults(el, {
        display: "flex",
        overflow: "auto",
        padding: "0 10px"
      });
      const cl = el.classList;
      const isSide = cl.contains("apx-legend-position-left") || cl.contains("apx-legend-position-right");
      const isTopOrBottom = cl.contains("apx-legend-position-top") || cl.contains("apx-legend-position-bottom");
      if (cl.contains("apexcharts-legend-group-horizontal")) {
        this.setStyleDefaults(el, { "flex-direction": "column" });
      }
      if (isSide) {
        this.setStyleDefaults(el, { "flex-direction": "column", bottom: "0" });
      }
      if (isTopOrBottom) {
        this.setStyleDefaults(el, { "flex-wrap": "wrap" });
      }
      if (isSide || isTopOrBottom && cl.contains("apexcharts-align-left")) {
        this.setStyleDefaults(el, {
          "justify-content": "flex-start",
          "align-items": "flex-start"
        });
      } else if (isTopOrBottom && cl.contains("apexcharts-align-center")) {
        this.setStyleDefaults(el, {
          "justify-content": "center",
          "align-items": "center"
        });
      } else if (isTopOrBottom && cl.contains("apexcharts-align-right")) {
        this.setStyleDefaults(el, {
          "justify-content": "flex-end",
          "align-items": "flex-end"
        });
      }
    });
    this.queryStyleable(clonedNode, ".apexcharts-legend-group").forEach(
      (el) => {
        this.setStyleDefaults(el, { display: "flex" });
      }
    );
    this.queryStyleable(
      clonedNode,
      ".apexcharts-legend-group-vertical"
    ).forEach((el) => {
      this.setStyleDefaults(el, { "flex-direction": "column-reverse" });
    });
    this.queryStyleable(clonedNode, ".apexcharts-legend-series").forEach(
      (el) => {
        this.setStyleDefaults(el, {
          cursor: el.classList.contains("apexcharts-no-click") ? "auto" : "pointer",
          "line-height": "normal",
          display: "flex",
          "align-items": "center"
        });
      }
    );
    this.queryStyleable(clonedNode, ".apexcharts-legend-text").forEach((el) => {
      this.setStyleDefaults(el, {
        position: "relative",
        "font-size": "14px"
      });
    });
    this.queryStyleable(clonedNode, ".apexcharts-legend-marker").forEach(
      (el) => {
        this.setStyleDefaults(el, {
          position: "relative",
          display: "flex",
          "align-items": "center",
          "justify-content": "center",
          cursor: "pointer",
          "margin-right": "1px"
        });
      }
    );
    this.queryStyleable(clonedNode, ".apexcharts-inactive-legend").forEach(
      (el) => {
        this.setStyleDefaults(el, { opacity: "0.45" });
      }
    );
    this.queryStyleable(
      clonedNode,
      ".apexcharts-legend .apexcharts-hidden-zero-series, .apexcharts-legend .apexcharts-hidden-null-series"
    ).forEach((el) => {
      el.style.setProperty("display", "none", "important");
    });
  }
  /**
   * The colour a raster export paints under the chart.
   *
   * A PNG needs an opaque base, so `dataURI` fills the canvas before drawing
   * the SVG onto it. That fill used to be `#fff` whenever `chart.background`
   * was unset *or* `'transparent'`, which is wrong for a dark theme:
   * `theme.mode: 'dark'` moves `chart.foreColor` to a near-white `#f6f7f8` but
   * leaves the background alone, so `background: 'transparent'` produced
   * near-white labels, axes and legend on white — a PNG that looked like it had
   * lost all its text. See #2920.
   *
   * The unset-background case was already covered: `Core.setupElements` paints
   * the SVG paper `#343A3F` for a dark theme, and the clone carries that inline
   * style into the export. `'transparent'` is the gap, because it makes that
   * paper style transparent too and nothing is left to cover the white fill.
   *
   * An explicit non-transparent `chart.background` still wins, including one
   * written by the Facet `--apx-surface` token (Theme assigns it into the same
   * field).
   * @returns {string}
   */
  resolveExportBackground() {
    const w = this.w;
    const bg = w.config.chart.background;
    if (bg && bg !== "transparent") return bg;
    return w.config.theme.mode === "dark" ? "#343A3F" : "#fff";
  }
  /**
   * Font families the rendered chart actually paints with.
   *
   * Read off the live DOM rather than the config: computed styles resolve
   * `chart.fontFamily`, every per-element `style.fontFamily` override, and any
   * family the chart inherits from the page, none of which are reliably
   * enumerable from config alone. The clone is not in the document, so its
   * computed styles would come back empty.
   * @returns {Set<string>}
   */
  collectFontFamilies() {
    const families = /* @__PURE__ */ new Set();
    const w = this.w;
    if (!Environment.isBrowser() || !w.dom.elWrap) return families;
    const els = this.queryStyleable(
      w.dom.elWrap,
      "text, tspan, .apexcharts-legend-text, .apexcharts-title-text, .apexcharts-subtitle-text"
    );
    const all = [w.dom.elWrap, ...els];
    all.forEach((el) => {
      const cs = (
        /** @type {any} */
        BrowserAPIs.getComputedStyle(
          /** @type {any} */
          el
        )
      );
      const ff = cs && cs.fontFamily;
      if (!ff) return;
      ff.split(",").forEach((name) => {
        const clean = name.trim().replace(/^['"]|['"]$/g, "");
        if (clean) families.add(clean.toLowerCase());
      });
    });
    return families;
  }
  /**
   * Every `@font-face` rule reachable from the document, as `{ family, css }`.
   *
   * Same-origin sheets are read through `cssRules`. A cross-origin sheet throws
   * on that access, so it is re-fetched by href and its `@font-face` blocks are
   * pulled out of the text: that is the path that matters in practice, since
   * hosted webfonts (the subject of #3617) are exactly the cross-origin case.
   * @returns {Promise<Array<{family: string, css: string}>>}
   */
  collectFontFaceRules() {
    if (!Environment.isBrowser()) return Promise.resolve([]);
    const found = [];
    const remote = [];
    const pushFromText = (cssText) => {
      const blocks = cssText.match(/@font-face\s*\{[^}]*\}/gi) || [];
      blocks.forEach((css) => {
        const m = css.match(/font-family\s*:\s*([^;}]+)/i);
        if (!m) return;
        const family = m[1].trim().replace(/^['"]|['"]$/g, "");
        found.push({ family: family.toLowerCase(), css });
      });
    };
    const sheets = Array.from(document.styleSheets || []);
    sheets.forEach((sheet) => {
      let rules = null;
      try {
        rules = sheet.cssRules;
      } catch (e) {
        rules = null;
      }
      if (rules) {
        Array.from(rules).forEach((rule) => {
          if (rule.type === 5 && rule.cssText) pushFromText(rule.cssText);
        });
        return;
      }
      if (sheet.href) {
        remote.push(
          fetch(sheet.href).then((r) => r.ok ? r.text() : "").then(pushFromText).catch(() => {
          })
        );
      }
    });
    return Promise.all(remote).then(() => found);
  }
  /**
   * Inline the `@font-face` rules for the families the chart uses, with the
   * font files themselves as base64 data URIs, into the exported SVG.
   *
   * Needed because the export is a standalone document: rasterizing it through
   * `<img src="data:image/svg+xml,...">` gives it no access to the page's
   * stylesheets *or* its loaded fonts, and an SVG-as-image may not fetch
   * external resources at all. Without this the text silently reflows into a
   * generic fallback face. See #3617.
   *
   * `@font-face` only exists as a stylesheet construct, so unlike the rules in
   * `applyExportStyles` this cannot be expressed as inline styles and has to
   * ship as a `<style>` element. A page whose CSP forbids inline styles will
   * drop it and fall back to today's behaviour, which is why the whole thing is
   * best-effort: any failure leaves the export exactly as it was.
   * @param {any} svgNode the parsed outer <svg> about to be serialized
   * @returns {Promise<void>}
   */
  embedFonts(svgNode) {
    const w = this.w;
    if (!Environment.isBrowser() || !w.config.chart.toolbar.export.embedFonts || typeof fetch !== "function") {
      return Promise.resolve();
    }
    const used = this.collectFontFamilies();
    if (!used.size) return Promise.resolve();
    return this.collectFontFaceRules().then((faces) => {
      const wanted = faces.filter((f) => used.has(f.family));
      if (!wanted.length) return Promise.resolve([]);
      return Promise.all(
        wanted.map(
          (face) => this.inlineFontFaceUrls(face.css).catch(() => null)
        )
      );
    }).then((cssBlocks) => {
      const css = (cssBlocks || []).filter(Boolean).join("\n");
      if (!css) return;
      const style = document.createElementNS(SVGNS, "style");
      style.textContent = css;
      svgNode.insertBefore(style, svgNode.firstChild);
    }).catch(() => {
    });
  }
  /**
   * Replace every remote `url(...)` in one `@font-face` block with a base64
   * data URI. Resolves to null if no url could be fetched, so the caller can
   * drop a block that would only reference unreachable files.
   * @param {string} css
   * @returns {Promise<string | null>}
   */
  inlineFontFaceUrls(css) {
    const urls = [];
    const re = /url\(\s*(['"]?)([^'")]+)\1\s*\)/gi;
    let m;
    while ((m = re.exec(css)) !== null) {
      if (!m[2].startsWith("data:")) urls.push(m[2]);
    }
    if (!urls.length) return Promise.resolve(css.includes("data:") ? css : null);
    return Promise.all(
      urls.map(
        (url) => this.fetchAsDataUri(url).then((dataUri) => ({ url, dataUri })).catch(() => ({ url, dataUri: null }))
      )
    ).then((results) => {
      let out = css;
      let replaced = 0;
      results.forEach(({ url, dataUri }) => {
        if (!dataUri) return;
        out = out.split(url).join(dataUri);
        replaced++;
      });
      return replaced ? out : null;
    });
  }
  /**
   * Fetch a binary asset as a base64 data URI.
   *
   * Uses `fetch` rather than the `<img>`+canvas route in `getBase64FromUrl`:
   * that route only works for raster images and taints the canvas for any
   * response without CORS headers, whereas this works for fonts too and fails
   * cleanly when CORS denies it.
   * @param {string} url
   * @returns {Promise<string>}
   */
  fetchAsDataUri(url) {
    if (typeof fetch !== "function" || typeof btoa !== "function") {
      return Promise.reject(new Error("fetch unavailable"));
    }
    return fetch(url).then((res) => {
      if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
      const type = res.headers.get("content-type") || "application/octet-stream";
      return res.arrayBuffer().then((buf) => {
        const bytes = new Uint8Array(buf);
        let binary = "";
        const CHUNK = 32768;
        for (let i = 0; i < bytes.length; i += CHUNK) {
          binary += String.fromCharCode.apply(
            null,
            /** @type {any} */
            bytes.subarray(i, i + CHUNK)
          );
        }
        return `data:${type};base64,${btoa(binary)}`;
      });
    });
  }
  /**
   * @param {number} [_scale]
   */
  getSvgString(_scale) {
    return new Promise((resolve) => {
      const w = this.w;
      let scale = _scale || w.config.chart.toolbar.export.scale || w.config.chart.toolbar.export.width / w.globals.svgWidth;
      if (!scale) {
        scale = 1;
      }
      const width = w.globals.svgWidth * scale;
      const height = w.globals.svgHeight * scale;
      const clonedNode = (
        /** @type {HTMLElement} */
        w.dom.elWrap.cloneNode(true)
      );
      clonedNode.style.width = width + "px";
      clonedNode.style.height = height + "px";
      this.inlineCanvasLayers(clonedNode);
      this.applyExportStyles(clonedNode);
      const serializedNode = new XMLSerializer().serializeToString(clonedNode);
      let svgString = `
        <svg xmlns="http://www.w3.org/2000/svg"
          version="1.1"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          class="apexcharts-svg"
          xmlns:data="ApexChartsNS"
          transform="translate(0, 0)"
          width="${w.globals.svgWidth}px" height="${w.globals.svgHeight}px">
          <foreignObject width="100%" height="100%">
            <div xmlns="http://www.w3.org/1999/xhtml" style="width:${width}px; height:${height}px;">
              ${serializedNode}
            </div>
          </foreignObject>
        </svg>
      `;
      const svgNode = this.svgStringToNode(svgString);
      if (scale !== 1) {
        this.scaleSvgNode(svgNode, scale);
      }
      Promise.all([
        this.convertImagesToBase64(svgNode),
        this.embedFonts(svgNode)
      ]).then(() => {
        svgString = new XMLSerializer().serializeToString(svgNode);
        resolve(svgString.replace(/&nbsp;/g, "&#160;"));
      });
    });
  }
  /**
   * Turn every remote `<image>` in the export into an inline data URI.
   *
   * This is not an optimisation: an SVG rasterized through `<img src="data:...">`
   * is not allowed to fetch external resources, so any href left pointing at a
   * URL vanishes from the PNG entirely. Image annotations and `hollow.image`
   * were disappearing from downloads for exactly this reason. See #3170.
   *
   * Two things were missing before. `SVGContainer.image()` writes `xlink:href`,
   * but Strata's inlined canvas layers and hand-authored `customSVG` markup use
   * the plain `href` form, and only the namespaced attribute was being read.
   * And conversion went through `<img>`+canvas, which taints (and therefore
   * throws) for any response without CORS headers, so a cross-origin icon,
   * the common case, silently failed. `fetch` is tried first and only falls
   * back to the canvas route, which still helps for a same-origin image on a
   * page whose CSP blocks `connect-src`.
   * @param {any} svgNode
   */
  convertImagesToBase64(svgNode) {
    const XLINK = "http://www.w3.org/1999/xlink";
    const images = svgNode.getElementsByTagName("image");
    const promises = Array.from(images).map((img) => {
      const nsHref = img.getAttributeNS(XLINK, "href");
      const plainHref = img.getAttribute("href");
      const href = nsHref || plainHref;
      if (!href || href.startsWith("data:")) return Promise.resolve();
      const write = (base64) => {
        if (nsHref) img.setAttributeNS(XLINK, "href", base64);
        if (plainHref || !nsHref) img.setAttribute("href", base64);
      };
      return this.fetchAsDataUri(href).then(write).catch(
        () => this.getBase64FromUrl(href).then(write).catch((error) => {
          console.error("Error converting image to base64:", error);
        })
      );
    });
    return Promise.all(promises);
  }
  /**
   * @param {string} url
   */
  getBase64FromUrl(url) {
    if (Environment.isSSR()) return Promise.resolve(url);
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.crossOrigin = "Anonymous";
      img.onload = () => {
        const canvas = document.createElement("canvas");
        canvas.width = img.width;
        canvas.height = img.height;
        const ctx = canvas.getContext("2d");
        if (ctx) ctx.drawImage(img, 0, 0);
        resolve(canvas.toDataURL());
      };
      img.onerror = reject;
      img.src = url;
    });
  }
  svgUrl() {
    return new Promise((resolve) => {
      this.getSvgString().then((svgData) => {
        const svgBlob = new Blob([svgData], {
          type: "image/svg+xml;charset=utf-8"
        });
        resolve(URL.createObjectURL(svgBlob));
      });
    });
  }
  /**
   * @param {Record<string, any> | undefined} options
   */
  dataURI(options) {
    if (Environment.isSSR()) return Promise.resolve({ imgURI: "" });
    return new Promise((resolve) => {
      const w = this.w;
      const scale = options ? options.scale || options.width / w.globals.svgWidth : 1;
      const canvas = document.createElement("canvas");
      canvas.width = w.globals.svgWidth * scale;
      canvas.height = parseInt(w.dom.elWrap.style.height, 10) * scale;
      const canvasBg = this.resolveExportBackground();
      const ctx = canvas.getContext("2d");
      if (!ctx) return;
      ctx.fillStyle = canvasBg;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      this.getSvgString(scale).then((svgData) => {
        const svgUrl = "data:image/svg+xml," + encodeURIComponent(svgData);
        const img = new Image();
        img.crossOrigin = "anonymous";
        img.onload = () => {
          ctx.drawImage(img, 0, 0);
          const edgeCanvas = canvas;
          if (edgeCanvas.msToBlob) {
            const blob = edgeCanvas.msToBlob();
            resolve({ blob });
          } else {
            const imgURI = canvas.toDataURL("image/png");
            resolve({ imgURI });
          }
        };
        img.src = svgUrl;
      });
    });
  }
  exportToSVG() {
    this.svgUrl().then((url) => {
      this.triggerDownload(
        url,
        this.w.config.chart.toolbar.export.svg.filename,
        ".svg"
      );
    });
  }
  exportToPng() {
    const scale = this.w.config.chart.toolbar.export.scale;
    const width = this.w.config.chart.toolbar.export.width;
    const option = scale ? { scale } : width ? { width } : void 0;
    this.dataURI(option).then(({ imgURI, blob }) => {
      if (blob) {
        navigator.msSaveOrOpenBlob(blob, this.w.globals.chartID + ".png");
      } else {
        this.triggerDownload(
          imgURI,
          this.w.config.chart.toolbar.export.png.filename,
          ".png"
        );
      }
    });
  }
  /** @param {{ series?: any, fileName?: any, columnDelimiter?: string, lineDelimiter?: string }} opts */
  exportToCSV({
    series,
    fileName,
    columnDelimiter = ",",
    lineDelimiter = "\n"
  }) {
    const w = this.w;
    if (!series) series = w.config.series;
    let columns = [];
    const rows = [];
    let result = "";
    const universalBOM = "\uFEFF";
    const gSeries = w.seriesData.series.map((s, i) => {
      return w.globals.collapsedSeriesIndices.indexOf(i) === -1 ? s : [];
    });
    const csvSafe = (val) => {
      if (val == null || Utils.isNumber(val)) return val;
      const s = String(val);
      return /^[=+\-@\t\r]/.test(s) ? `'${s}` : s;
    };
    const getFormattedCategory = (cat) => {
      if (typeof w.config.chart.toolbar.export.csv.categoryFormatter === "function") {
        return w.config.chart.toolbar.export.csv.categoryFormatter(cat);
      }
      if (w.config.xaxis.type === "datetime" && String(cat).length >= 10) {
        return new Date(cat).toDateString();
      }
      return Utils.isNumber(cat) ? cat : csvSafe(cat.split(columnDelimiter).join(""));
    };
    const getFormattedValue = (value) => {
      return typeof w.config.chart.toolbar.export.csv.valueFormatter === "function" ? w.config.chart.toolbar.export.csv.valueFormatter(value) : csvSafe(value);
    };
    const seriesMaxDataLength = Math.max(
      ...series.map((s) => {
        return s.data ? s.data.length : 0;
      })
    );
    const dataFormat = new Data(this.w);
    const axesUtils = new AxesUtils(this.w, {
      theme: this.ctx.theme,
      timeScale: this.ctx.timeScale
    });
    const getCat = (i) => {
      let cat = "";
      if (!w.globals.axisCharts) {
        cat = w.config.labels[i];
      } else {
        if (w.config.xaxis.type === "category" || w.config.xaxis.convertedCatToNumeric) {
          if (w.globals.isBarHorizontal) {
            const lbFormatter = w.formatters.yLabelFormatters[0];
            const sr = new Series(this.ctx.w);
            const activeSeries = sr.getActiveConfigSeriesIndex();
            cat = lbFormatter(w.labelData.labels[i], {
              seriesIndex: activeSeries,
              dataPointIndex: i,
              w
            });
          } else {
            cat = axesUtils.getLabel(
              w.labelData.labels,
              w.labelData.timescaleLabels,
              0,
              i
            ).text;
          }
        }
        if (w.config.xaxis.type === "datetime") {
          if (w.config.xaxis.categories.length) {
            cat = w.config.xaxis.categories[i];
          } else if (w.config.labels.length) {
            cat = w.config.labels[i];
          }
        }
      }
      if (cat === null) return "nullvalue";
      if (Array.isArray(cat)) {
        cat = cat.join(" ");
      }
      return Utils.isNumber(cat) ? cat : cat.split(columnDelimiter).join("");
    };
    const getEmptyDataForCsvColumn = () => {
      return [...Array(seriesMaxDataLength)].map(() => "");
    };
    const handleAxisRowsColumns = (s, sI) => {
      var _a, _b, _c, _d, _e, _f;
      if (columns.length && sI === 0) {
        rows.push(columns.join(columnDelimiter));
      }
      if (s.data) {
        const rowData = s.data.length ? s.data : getEmptyDataForCsvColumn();
        for (let i = 0; i < rowData.length; i++) {
          columns = [];
          let cat = getCat(i);
          if (cat === "nullvalue") continue;
          if (!cat) {
            if (dataFormat.isFormatXY()) {
              cat = series[sI].data[i].x;
            } else if (dataFormat.isFormat2DArray()) {
              cat = series[sI].data[i] ? series[sI].data[i][0] : "";
            }
          }
          if (sI === 0) {
            columns.push(getFormattedCategory(cat));
            for (let ci = 0; ci < w.seriesData.series.length; ci++) {
              const value = dataFormat.isFormatXY() ? (_a = series[ci].data[i]) == null ? void 0 : _a.y : gSeries[ci][i];
              columns.push(getFormattedValue(value));
            }
          }
          if (w.config.chart.type === "candlestick" || s.type && s.type === "candlestick") {
            columns.pop();
            columns.push(w.candleData.seriesCandleO[sI][i]);
            columns.push(w.candleData.seriesCandleH[sI][i]);
            columns.push(w.candleData.seriesCandleL[sI][i]);
            columns.push(w.candleData.seriesCandleC[sI][i]);
          }
          if (w.config.chart.type === "boxPlot" || s.type && s.type === "boxPlot") {
            columns.pop();
            columns.push(w.candleData.seriesCandleO[sI][i]);
            columns.push(w.candleData.seriesCandleH[sI][i]);
            columns.push(w.candleData.seriesCandleM[sI][i]);
            columns.push(w.candleData.seriesCandleL[sI][i]);
            columns.push(w.candleData.seriesCandleC[sI][i]);
          }
          if (w.config.chart.type === "rangeBar") {
            columns.pop();
            columns.push(w.rangeData.seriesRangeStart[sI][i]);
            columns.push(w.rangeData.seriesRangeEnd[sI][i]);
          }
          if (w.config.chart.type === "violin" || s.type && s.type === "violin") {
            columns.pop();
            columns.push((_b = w.violinData.seriesViolinMin[sI]) == null ? void 0 : _b[i]);
            columns.push((_c = w.violinData.seriesViolinMax[sI]) == null ? void 0 : _c[i]);
            columns.push((_f = (_e = (_d = w.violinData.seriesViolinPoints[sI]) == null ? void 0 : _d[i]) == null ? void 0 : _e.length) != null ? _f : 0);
          }
          if (columns.length) {
            rows.push(columns.join(columnDelimiter));
          }
        }
      }
    };
    const handleUnequalXValues = () => {
      const categories = /* @__PURE__ */ new Set();
      const data = {};
      series.forEach((s, sI) => {
        s == null ? void 0 : s.data.forEach((dataItem) => {
          let cat, value;
          if (dataFormat.isFormatXY()) {
            cat = dataItem.x;
            value = dataItem.y;
          } else if (dataFormat.isFormat2DArray()) {
            cat = dataItem[0];
            value = dataItem[1];
          } else {
            return;
          }
          if (!/** @type {Record<string,any>} */
          data[cat]) {
            data[cat] = Array(
              series.length
            ).fill("");
          }
          data[cat][sI] = getFormattedValue(value);
          categories.add(cat);
        });
      });
      if (columns.length) {
        rows.push(columns.join(columnDelimiter));
      }
      Array.from(categories).sort().forEach((cat) => {
        const values = (
          /** @type {Record<string,any>} */
          data[cat]
        );
        rows.push([getFormattedCategory(cat), ...values].join(columnDelimiter));
      });
    };
    columns.push(w.config.chart.toolbar.export.csv.headerCategory);
    if (w.config.chart.type === "boxPlot") {
      columns.push("minimum");
      columns.push("q1");
      columns.push("median");
      columns.push("q3");
      columns.push("maximum");
    } else if (w.config.chart.type === "candlestick") {
      columns.push("open");
      columns.push("high");
      columns.push("low");
      columns.push("close");
    } else if (w.config.chart.type === "rangeBar") {
      columns.push("minimum");
      columns.push("maximum");
    } else if (w.config.chart.type === "violin") {
      columns.push("minimum");
      columns.push("maximum");
      columns.push("observations");
    } else {
      series.map((s, sI) => {
        const sname = (s.name ? s.name : `series-${sI}`) + "";
        if (w.globals.axisCharts) {
          columns.push(
            sname.split(columnDelimiter).join("") ? sname.split(columnDelimiter).join("") : `series-${sI}`
          );
        }
      });
    }
    if (!w.globals.axisCharts) {
      columns.push(w.config.chart.toolbar.export.csv.headerValue);
      rows.push(columns.join(columnDelimiter));
    }
    if (!w.globals.allSeriesHasEqualX && w.globals.axisCharts && !w.config.xaxis.categories.length && !w.config.labels.length) {
      handleUnequalXValues();
    } else {
      series.map((s, sI) => {
        if (w.globals.axisCharts) {
          handleAxisRowsColumns(s, sI);
        } else {
          columns = [];
          columns.push(getFormattedCategory(w.labelData.labels[sI]));
          columns.push(getFormattedValue(gSeries[sI]));
          rows.push(columns.join(columnDelimiter));
        }
      });
    }
    result += rows.join(lineDelimiter);
    this.triggerDownload(
      "data:text/csv; charset=utf-8," + encodeURIComponent(universalBOM + result),
      fileName ? fileName : w.config.chart.toolbar.export.csv.filename,
      ".csv"
    );
  }
  /**
   * @param {string} href
   * @param {string} filename
   * @param {string} ext
   */
  triggerDownload(href, filename, ext) {
    if (Environment.isSSR()) return;
    const downloadLink = document.createElement("a");
    downloadLink.href = href;
    downloadLink.download = (filename ? filename : this.w.globals.chartID) + ext;
    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);
  }
}
const icoPan = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <path d="M5 9 2 12l3 3"/>\n    <path d="M9 5l3-3 3 3"/>\n    <path d="M15 19l-3 3-3-3"/>\n    <path d="M19 9l3 3-3 3"/>\n    <path d="M2 12h20"/>\n    <path d="M12 2v20"/>\n</svg>\n';
const icoZoom = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <circle cx="11" cy="11" r="7"/>\n    <path d="m21 21-4.3-4.3M8 11h6M11 8v6"/>\n</svg>\n';
const icoReset = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>\n    <path d="M3 3v5h5"/>\n</svg>\n';
const icoZoomIn = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <path d="M12 5v14M5 12h14"/>\n</svg>\n';
const icoZoomOut = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <path d="M5 12h14"/>\n</svg>\n';
const icoSelect = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <path d="M5 3a2 2 0 0 0-2 2"/>\n    <path d="M19 3a2 2 0 0 1 2 2"/>\n    <path d="M21 19a2 2 0 0 1-2 2"/>\n    <path d="M5 21a2 2 0 0 1-2-2"/>\n    <path d="M9 3h1M14 3h1M9 21h1M14 21h1M3 9v1M3 14v1M21 9v1M21 14v1"/>\n</svg>\n';
const icoMeasure = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.4 2.4 0 0 1 0-3.4l2.6-2.6a2.4 2.4 0 0 1 3.4 0Z"/>\n    <path d="m14.5 12.5 2-2"/>\n    <path d="m11.5 9.5 2-2"/>\n    <path d="m8.5 6.5 2-2"/>\n    <path d="m17.5 15.5 2-2"/>\n</svg>\n';
const icoMenu = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n    <path d="M4 6h16M4 12h16M4 18h16"/>\n</svg>\n';
class Toolbar {
  /**
   * @param {import('../types/internal').ChartStateW} w
   * @param {import('../types/internal').ChartContext} ctx
   */
  constructor(w, ctx) {
    this.w = w;
    this.ctx = ctx;
    this.ev = this.w.config.chart.events;
    this.selectedClass = "apexcharts-selected";
    this.localeValues = this.w.globals.locale.toolbar;
    this.minX = w.globals.minX;
    this.maxX = w.globals.maxX;
    this.elZoom = null;
    this.elZoomIn = null;
    this.elZoomOut = null;
    this.elPan = null;
    this.elSelection = null;
    this.elMeasure = null;
    this.elZoomReset = null;
    this.elMenuIcon = null;
    this.elMenu = null;
    this.elMenuItems = [];
    this.t = null;
  }
  createToolbar() {
    var _a, _b;
    const w = this.w;
    const createDiv = () => {
      return BrowserAPIs.createElementNS("http://www.w3.org/1999/xhtml", "div");
    };
    const createBtn = () => {
      const btn = (
        /** @type {HTMLButtonElement} */
        BrowserAPIs.createElementNS("http://www.w3.org/1999/xhtml", "button")
      );
      btn.setAttribute("type", "button");
      return btn;
    };
    const elToolbarWrap = createDiv();
    elToolbarWrap.setAttribute("class", "apexcharts-toolbar");
    elToolbarWrap.style.top = w.config.chart.toolbar.offsetY + "px";
    elToolbarWrap.style.right = -w.config.chart.toolbar.offsetX + 3 + "px";
    w.dom.elWrap.appendChild(elToolbarWrap);
    this.elZoom = createBtn();
    this.elZoomIn = createBtn();
    this.elZoomOut = createBtn();
    this.elPan = createBtn();
    this.elSelection = createBtn();
    this.elMeasure = createBtn();
    this.elZoomReset = createBtn();
    this.elMenuIcon = createBtn();
    this.elMenu = createDiv();
    this.elCustomIcons = [];
    this.t = w.config.chart.toolbar.tools;
    if (Array.isArray(this.t.customIcons)) {
      for (let i = 0; i < this.t.customIcons.length; i++) {
        this.elCustomIcons.push(createBtn());
      }
    }
    const toolbarControls = [];
    const appendZoomControl = (type, el, ico) => {
      const tool = type.toLowerCase();
      if (this.t[tool] && w.config.chart.zoom.enabled) {
        toolbarControls.push({
          el,
          icon: typeof this.t[tool] === "string" ? this.t[tool] : ico,
          title: (
            /** @type {any} */
            this.localeValues[type]
          ),
          class: `apexcharts-${tool}-icon`
        });
      }
    };
    appendZoomControl("zoomIn", this.elZoomIn, icoZoomIn);
    appendZoomControl("zoomOut", this.elZoomOut, icoZoomOut);
    const zoomSelectionCtrls = (z) => {
      if (this.t[z] && w.config.chart[z].enabled) {
        toolbarControls.push({
          el: z === "zoom" ? this.elZoom : this.elSelection,
          icon: typeof this.t[z] === "string" ? this.t[z] : z === "zoom" ? icoZoom : icoSelect,
          title: (
            /** @type {any} */
            this.localeValues[z === "zoom" ? "selectionZoom" : "selection"]
          ),
          class: `apexcharts-${z}-icon`
        });
      }
    };
    zoomSelectionCtrls("zoom");
    zoomSelectionCtrls("selection");
    if (this.t.pan && w.config.chart.zoom.enabled) {
      toolbarControls.push({
        el: this.elPan,
        icon: typeof this.t.pan === "string" ? this.t.pan : icoPan,
        title: this.localeValues.pan,
        class: "apexcharts-pan-icon"
      });
    }
    if (this.t.measure && w.config.chart.measure && w.config.chart.measure.enabled) {
      toolbarControls.push({
        el: this.elMeasure,
        icon: typeof this.t.measure === "string" ? this.t.measure : icoMeasure,
        title: (
          /** @type {any} */
          this.localeValues.measure || "Measure"
        ),
        class: "apexcharts-measure-icon"
      });
    }
    appendZoomControl("reset", this.elZoomReset, icoReset);
    if (this.t.download) {
      toolbarControls.push({
        el: this.elMenuIcon,
        icon: typeof this.t.download === "string" ? this.t.download : icoMenu,
        title: this.localeValues.menu,
        class: "apexcharts-menu-icon"
      });
    }
    for (let i = 0; i < this.elCustomIcons.length; i++) {
      toolbarControls.push({
        el: this.elCustomIcons[i],
        icon: this.t.customIcons[i].icon,
        title: this.t.customIcons[i].title,
        index: this.t.customIcons[i].index,
        class: "apexcharts-toolbar-custom-icon " + this.t.customIcons[i].class
      });
    }
    toolbarControls.forEach((t, index) => {
      if (t.index) {
        Utils.moveIndexInArray(toolbarControls, index, t.index);
      }
    });
    for (let i = 0; i < toolbarControls.length; i++) {
      Graphics.setAttrs(toolbarControls[i].el, {
        class: toolbarControls[i].class,
        title: toolbarControls[i].title,
        "aria-label": toolbarControls[i].title
      });
      toolbarControls[i].el.innerHTML = toolbarControls[i].icon;
      elToolbarWrap.appendChild(toolbarControls[i].el);
    }
    if (this.elZoom.parentNode) {
      this.elZoom.setAttribute("aria-pressed", String(!!w.interact.zoomEnabled));
    }
    if (this.elSelection.parentNode) {
      this.elSelection.setAttribute(
        "aria-pressed",
        String(!!w.interact.selectionEnabled)
      );
    }
    if (this.elPan.parentNode) {
      this.elPan.setAttribute("aria-pressed", String(!!w.interact.panEnabled));
    }
    if (this.elMeasure.parentNode) {
      this.elMeasure.setAttribute(
        "aria-pressed",
        String(!!w.interact.measureEnabled)
      );
    }
    if (this.elMenuIcon.parentNode) {
      this.elMenuIcon.setAttribute("aria-haspopup", "true");
      this.elMenuIcon.setAttribute("aria-expanded", "false");
    }
    this._createHamburgerMenu(elToolbarWrap);
    if (w.interact.zoomEnabled) {
      this.elZoom.classList.add(this.selectedClass);
    } else if (w.interact.panEnabled) {
      this.elPan.classList.add(this.selectedClass);
    } else if (w.interact.selectionEnabled) {
      this.elSelection.classList.add(this.selectedClass);
    } else if (w.interact.measureEnabled && this.elMeasure) {
      this.elMeasure.classList.add(this.selectedClass);
      (_b = (_a = this.ctx.measure) == null ? void 0 : _a.startMeasure) == null ? void 0 : _b.call(_a);
    }
    this.addToolbarEventListeners();
  }
  /**
   * @param {Element} parent
   */
  _createHamburgerMenu(parent) {
    this.elMenuItems = [];
    parent.appendChild(
      /** @type {Node} */
      this.elMenu
    );
    Graphics.setAttrs(this.elMenu, {
      class: "apexcharts-menu",
      role: "menu"
    });
    const menuItems = [
      {
        name: "exportSVG",
        title: this.localeValues.exportToSVG
      },
      {
        name: "exportPNG",
        title: this.localeValues.exportToPNG
      },
      {
        name: "exportCSV",
        title: this.localeValues.exportToCSV
      }
    ];
    for (let i = 0; i < menuItems.length; i++) {
      this.elMenuItems.push(
        BrowserAPIs.createElementNS("http://www.w3.org/1999/xhtml", "div")
      );
      this.elMenuItems[i].innerHTML = menuItems[i].title;
      Graphics.setAttrs(this.elMenuItems[i], {
        class: `apexcharts-menu-item ${menuItems[i].name}`,
        title: menuItems[i].title,
        role: "menuitem",
        tabindex: "-1"
      });
      this.elMenu.appendChild(this.elMenuItems[i]);
    }
  }
  addToolbarEventListeners() {
    var _a, _b, _c, _d, _e, _f, _g, _h, _i;
    (_a = this.elZoomReset) == null ? void 0 : _a.addEventListener("click", this.handleZoomReset.bind(this));
    (_b = this.elSelection) == null ? void 0 : _b.addEventListener(
      "click",
      this.toggleZoomSelection.bind(this, "selection")
    );
    (_c = this.elZoom) == null ? void 0 : _c.addEventListener(
      "click",
      this.toggleZoomSelection.bind(this, "zoom")
    );
    (_d = this.elZoomIn) == null ? void 0 : _d.addEventListener("click", this.handleZoomIn.bind(this));
    (_e = this.elZoomOut) == null ? void 0 : _e.addEventListener("click", this.handleZoomOut.bind(this));
    (_f = this.elPan) == null ? void 0 : _f.addEventListener("click", this.togglePanning.bind(this));
    (_g = this.elMeasure) == null ? void 0 : _g.addEventListener("click", this.toggleMeasure.bind(this));
    (_h = this.elMenuIcon) == null ? void 0 : _h.addEventListener("click", this.toggleMenu.bind(this));
    this.elMenuItems.forEach((m) => {
      if (m.classList.contains("exportSVG")) {
        m.addEventListener("click", this.handleDownload.bind(this, "svg"));
      } else if (m.classList.contains("exportPNG")) {
        m.addEventListener("click", this.handleDownload.bind(this, "png"));
      } else if (m.classList.contains("exportCSV")) {
        m.addEventListener("click", this.handleDownload.bind(this, "csv"));
      }
    });
    for (let i = 0; i < this.t.customIcons.length; i++) {
      this.elCustomIcons[i].addEventListener(
        "click",
        this.t.customIcons[i].click.bind(this, this.ctx, this.ctx.w)
      );
    }
    const toolbarButtons = [
      this.elZoomReset,
      this.elSelection,
      this.elZoom,
      this.elZoomIn,
      this.elZoomOut,
      this.elPan,
      this.elMeasure,
      this.elMenuIcon,
      ...this.elCustomIcons
    ];
    toolbarButtons.forEach((btn) => {
      btn.addEventListener("keydown", (e) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          const btnClass = btn.className;
          btn.click();
          requestAnimationFrame(() => {
            const baseEl = this.w.dom.baseEl;
            if (!baseEl) return;
            const apexClass = btnClass.split(" ").find((c) => c.startsWith("apexcharts-"));
            if (!apexClass) return;
            const restored = baseEl.querySelector(`.${apexClass}`);
            if (restored) restored.focus();
          });
        }
      });
    });
    (_i = this.elMenuIcon) == null ? void 0 : _i.addEventListener(
      "keydown",
      (e) => {
        var _a2;
        if (e.key === "ArrowDown" || e.key === "ArrowUp") {
          e.preventDefault();
          if (!((_a2 = this.elMenu) == null ? void 0 : _a2.classList.contains("apexcharts-menu-open"))) {
            this.toggleMenu();
          }
          window.setTimeout(() => {
            const idx = e.key === "ArrowDown" ? 0 : this.elMenuItems.length - 1;
            if (this.elMenuItems[idx])
              this.elMenuItems[idx].focus();
          }, 20);
        }
      }
    );
    this.elMenuItems.forEach((m, idx) => {
      m.addEventListener("keydown", (e) => {
        var _a2;
        if (e.key === "ArrowDown") {
          e.preventDefault();
          const next = this.elMenuItems[idx + 1] || this.elMenuItems[0];
          next.focus();
        } else if (e.key === "ArrowUp") {
          e.preventDefault();
          const prev = this.elMenuItems[idx - 1] || this.elMenuItems[this.elMenuItems.length - 1];
          prev.focus();
        } else if (e.key === "Escape" || e.key === "Tab") {
          this._closeMenu();
          (_a2 = this.elMenuIcon) == null ? void 0 : _a2.focus();
          if (e.key === "Tab") ;
          else {
            e.preventDefault();
          }
        } else if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          m.click();
        }
      });
    });
  }
  /**
   * @param {string} type
   */
  toggleZoomSelection(type) {
    const charts = this.ctx.getSyncedCharts();
    charts.forEach((ch) => {
      const tb = ch.ctx.toolbar;
      const enabledType = type === "selection" ? "selectionEnabled" : "zoomEnabled";
      const wasEnabled = !!ch.w.globals[enabledType];
      tb.toggleOtherControls();
      const el = type === "selection" ? tb.elSelection : tb.elZoom;
      if (!wasEnabled) {
        ch.w.globals[enabledType] = true;
        el.classList.add(tb.selectedClass);
      }
      el.setAttribute("aria-pressed", String(!!ch.w.globals[enabledType]));
    });
  }
  /**
   * Toggle the measure ruler tool. Mutually exclusive with zoom/pan/selection
   * (toggleOtherControls deselects those and disarms any active measure), so a
   * fresh enable arms the ruler via the Measure module's sticky mode.
   */
  toggleMeasure() {
    var _a, _b, _c, _d;
    const w = this.w;
    const enabling = !w.interact.measureEnabled;
    this.toggleOtherControls();
    if (enabling) {
      w.interact.measureEnabled = true;
      (_a = this.elMeasure) == null ? void 0 : _a.classList.add(this.selectedClass);
      (_c = (_b = this.ctx.measure) == null ? void 0 : _b.startMeasure) == null ? void 0 : _c.call(_b);
    }
    (_d = this.elMeasure) == null ? void 0 : _d.setAttribute(
      "aria-pressed",
      String(w.interact.measureEnabled)
    );
  }
  getToolbarIconsReference() {
    const w = this.w;
    if (!this.elZoom) {
      this.elZoom = w.dom.baseEl.querySelector(".apexcharts-zoom-icon");
    }
    if (!this.elPan) {
      this.elPan = w.dom.baseEl.querySelector(".apexcharts-pan-icon");
    }
    if (!this.elSelection) {
      this.elSelection = w.dom.baseEl.querySelector(
        ".apexcharts-selection-icon"
      );
    }
    if (!this.elMeasure) {
      this.elMeasure = w.dom.baseEl.querySelector(".apexcharts-measure-icon");
    }
  }
  /**
   * @param {string} type
   */
  enableZoomPanFromToolbar(type) {
    this.toggleOtherControls();
    type === "pan" ? this.w.interact.panEnabled = true : this.w.interact.zoomEnabled = true;
    const el = type === "pan" ? this.elPan : this.elZoom;
    const el2 = type === "pan" ? this.elZoom : this.elPan;
    if (el) {
      el.classList.add(this.selectedClass);
    }
    if (el2) {
      el2.classList.remove(this.selectedClass);
    }
  }
  togglePanning() {
    const charts = this.ctx.getSyncedCharts();
    charts.forEach((ch) => {
      const tb = ch.ctx.toolbar;
      const wasEnabled = !!ch.w.interact.panEnabled;
      tb.toggleOtherControls();
      if (!wasEnabled) {
        ch.w.interact.panEnabled = true;
        tb.elPan.classList.add(tb.selectedClass);
      }
      tb.elPan.setAttribute("aria-pressed", String(!!ch.w.interact.panEnabled));
    });
  }
  toggleOtherControls() {
    var _a, _b, _c;
    const w = this.w;
    w.interact.panEnabled = false;
    w.interact.zoomEnabled = false;
    w.interact.selectionEnabled = false;
    if (w.interact.measureEnabled) {
      w.interact.measureEnabled = false;
      (_b = (_a = this.ctx.measure) == null ? void 0 : _a.stopMeasure) == null ? void 0 : _b.call(_a);
      (_c = this.elMeasure) == null ? void 0 : _c.setAttribute("aria-pressed", "false");
    }
    this.getToolbarIconsReference();
    const toggleEls = [this.elPan, this.elSelection, this.elZoom, this.elMeasure];
    toggleEls.forEach((el) => {
      if (el) {
        el.classList.remove(this.selectedClass);
      }
    });
  }
  /**
   * Read the current x-range from globals at click time.
   * Toolbar instance is kept alive across updates (Phase 8 lazy
   * instantiation), so cached this.minX/maxX go stale after a zoom.
   * @returns {{minX: number, maxX: number}}
   */
  _currentXRange() {
    const w = this.w;
    if (w.axisFlags.isRangeBar) {
      return { minX: w.globals.minY, maxX: w.globals.maxY };
    }
    return { minX: w.globals.minX, maxX: w.globals.maxX };
  }
  handleZoomIn() {
    const w = this.w;
    const { minX, maxX } = this._currentXRange();
    this.minX = minX;
    this.maxX = maxX;
    const centerX = (minX + maxX) / 2;
    const newMinX = (minX + centerX) / 2;
    const newMaxX = (maxX + centerX) / 2;
    const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX);
    if (!w.interact.disableZoomIn) {
      this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX);
    }
  }
  handleZoomOut() {
    const w = this.w;
    const { minX, maxX } = this._currentXRange();
    this.minX = minX;
    this.maxX = maxX;
    if (w.config.xaxis.type === "datetime" && new Date(minX).getUTCFullYear() < 1e3) {
      return;
    }
    const centerX = (minX + maxX) / 2;
    const newMinX = minX - (centerX - minX);
    const newMaxX = maxX - (centerX - maxX);
    const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX);
    if (!w.interact.disableZoomOut) {
      this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX);
    }
  }
  /**
   * @param {number} newMinX
   * @param {number} newMaxX
   */
  _getNewMinXMaxX(newMinX, newMaxX) {
    const shouldFloor = this.w.config.xaxis.convertedCatToNumeric;
    return {
      minX: shouldFloor ? Math.floor(newMinX) : newMinX,
      maxX: shouldFloor ? Math.floor(newMaxX) : newMaxX
    };
  }
  /**
   * @param {number} newMinX
   * @param {number} newMaxX
   */
  zoomUpdateOptions(newMinX, newMaxX) {
    const w = this.w;
    if (newMinX === void 0 && newMaxX === void 0) {
      this.handleZoomReset();
      return;
    }
    if (w.config.xaxis.convertedCatToNumeric) {
      if (newMinX < 1) {
        newMinX = 1;
        newMaxX = w.globals.dataPoints;
      }
      if (newMaxX - newMinX < 2) {
        return;
      }
    }
    let xaxis = {
      min: newMinX,
      max: newMaxX
    };
    const beforeZoomRange = this.getBeforeZoomRange(
      xaxis,
      /** @type {any} */
      void 0
    );
    if (beforeZoomRange) {
      xaxis = beforeZoomRange.xaxis;
    }
    const options = {
      xaxis
    };
    if (!w.globals.initialConfig) return;
    const yaxis = Utils.clone(w.globals.initialConfig.yaxis);
    if (!w.config.chart.group) {
      options.yaxis = yaxis;
    }
    this.w.interact.zoomed = true;
    this.ctx.updateHelpers._updateOptions(
      options,
      false,
      this.w.config.chart.animations.dynamicAnimation.enabled
    );
    this.zoomCallback(xaxis, yaxis);
  }
  /**
   * @param {Record<string, any>} xaxis
   * @param {Record<string, any>} yaxis
   */
  zoomCallback(xaxis, yaxis) {
    if (typeof this.ev.zoomed === "function") {
      this.ev.zoomed(this.ctx, { xaxis, yaxis });
      this.ctx.events.fireEvent("zoomed", { xaxis, yaxis });
    }
  }
  /**
   * @param {Record<string, any>} xaxis
   * @param {Record<string, any>} yaxis
   */
  getBeforeZoomRange(xaxis, yaxis) {
    let newRange = null;
    if (typeof this.ev.beforeZoom === "function") {
      newRange = this.ev.beforeZoom(this, { xaxis, yaxis });
    }
    return newRange;
  }
  toggleMenu() {
    window.setTimeout(() => {
      var _a, _b, _c;
      if ((_a = this.elMenu) == null ? void 0 : _a.classList.contains("apexcharts-menu-open")) {
        this._closeMenu();
      } else {
        (_b = this.elMenu) == null ? void 0 : _b.classList.add("apexcharts-menu-open");
        (_c = this.elMenuIcon) == null ? void 0 : _c.setAttribute("aria-expanded", "true");
      }
    }, 0);
  }
  _closeMenu() {
    var _a, _b;
    (_a = this.elMenu) == null ? void 0 : _a.classList.remove("apexcharts-menu-open");
    (_b = this.elMenuIcon) == null ? void 0 : _b.setAttribute("aria-expanded", "false");
  }
  /**
   * @param {string} type
   */
  handleDownload(type) {
    const w = this.w;
    const exprt = new Exports(this.w, this.ctx);
    switch (type) {
      case "svg":
        exprt.exportToSVG();
        break;
      case "png":
        exprt.exportToPng();
        break;
      case "csv":
        exprt.exportToCSV({
          series: w.config.series,
          columnDelimiter: w.config.chart.toolbar.export.csv.columnDelimiter
        });
        break;
    }
  }
  handleZoomReset() {
    const charts = this.ctx.getSyncedCharts();
    charts.forEach((ch) => {
      const w = ch.w;
      if (!w.interact.zoomed) return;
      w.globals.lastXAxis.min = w.globals.initialConfig.xaxis.min;
      w.globals.lastXAxis.max = w.globals.initialConfig.xaxis.max;
      ch.updateHelpers.revertDefaultAxisMinMax();
      if (typeof w.config.chart.events.beforeResetZoom === "function") {
        const resetZoomRange = w.config.chart.events.beforeResetZoom(ch, w);
        if (resetZoomRange) {
          ch.updateHelpers.revertDefaultAxisMinMax(resetZoomRange);
        }
      }
      if (typeof w.config.chart.events.zoomed === "function") {
        ch.ctx.toolbar.zoomCallback({
          min: w.config.xaxis.min,
          max: w.config.xaxis.max
        });
      }
      const series = ch.ctx.series.emptyCollapsedSeries(
        Utils.clone(w.globals.initialSeries)
      );
      ch.updateHelpers._updateSeries(
        series,
        w.config.chart.animations.dynamicAnimation.enabled
      );
      w.interact.zoomed = false;
    });
  }
  destroy() {
    this.elZoom = null;
    this.elZoomIn = null;
    this.elZoomOut = null;
    this.elPan = null;
    this.elSelection = null;
    this.elMeasure = null;
    this.elZoomReset = null;
    this.elMenuIcon = null;
  }
}
class AxisMapping {
  /**
   * Pixels per data-unit on the x-axis. Derived from `minX..maxX` so it is the
   * exact inverse used by both {@link dataXToPx} and {@link pxToDataX}.
   * @param {import('../types/internal').ChartStateW} w
   * @returns {number}
   */
  static xRatio(w) {
    const gw = w.layout.gridWidth || 1;
    return (w.globals.maxX - w.globals.minX) / gw;
  }
  /**
   * Data-x -> pixels from the plot origin (usable as an SVG `x` attribute).
   * @param {import('../types/internal').ChartStateW} w
   * @param {number} dataX
   * @returns {number}
   */
  static dataXToPx(w, dataX) {
    return (dataX - w.globals.minX) / AxisMapping.xRatio(w);
  }
  /**
   * Pixels from the plot origin -> data-x. Feed it `screenX - svgLeft - translateX`.
   * @param {import('../types/internal').ChartStateW} w
   * @param {number} px
   * @returns {number}
   */
  static pxToDataX(w, px) {
    return w.globals.minX + px * AxisMapping.xRatio(w);
  }
  /**
   * Client (screen) x -> pixels from the plot origin. The origin is the svg
   * element's left edge plus `translateX`, never the `.apexcharts-grid` box
   * (fact 2 above), so the result does not depend on what the grid happens to
   * render. `svgWidth` is the unscaled width the svg was drawn at, so the ratio
   * against the measured one is the CSS zoom of any container the chart sits in.
   * @param {import('../types/internal').ChartStateW} w
   * @param {number} screenX
   * @returns {number}
   */
  static screenXToPlotPx(w, screenX) {
    const baseEl = w.dom.baseEl;
    const svg = baseEl && baseEl.querySelector(".apexcharts-svg");
    if (!svg) return screenX - w.layout.translateX;
    const svgRect = svg.getBoundingClientRect();
    const zoom = w.globals.svgWidth ? svgRect.width / w.globals.svgWidth : 1;
    return (screenX - svgRect.left) / (zoom || 1) - w.layout.translateX;
  }
}
const Box = _core.__apex_index_Box;
const WHEEL_ZOOM_PIXELS_PER_2X = 240;
const INERTIA_MIN_RELEASE_VELOCITY = 0.05;
const INERTIA_DEFAULT_FRICTION = 0.92;
const INERTIA_STOP_VELOCITY = 0.02;
const FRAME_MS_60FPS = 16.6667;
const PAN_NUDGE_DIVISOR = 15;
class ZoomPanSelection extends Toolbar {
  /**
   * @param {import('../types/internal').ChartStateW} w
   * @param {import('../types/internal').ChartContext} ctx
   */
  constructor(w, ctx) {
    super(w, ctx);
    this.w = w;
    this.ctx = ctx;
    this.dragged = false;
    this.graphics = new Graphics(this.w);
    this.eventList = [
      "mousedown",
      "mouseleave",
      "mousemove",
      "touchstart",
      "touchmove",
      "mouseup",
      "touchend"
    ];
    this.clientX = 0;
    this.clientY = 0;
    this.startX = 0;
    this.endX = 0;
    this.dragX = 0;
    this.startY = 0;
    this.endY = 0;
    this.dragY = 0;
    this.moveDirection = "none";
  }
  /** @param {{xyRatios: any}} opts */
  init({ xyRatios }) {
    const w = this.w;
    const me = this;
    this.xyRatios = xyRatios;
    this.zoomRect = this.graphics.drawRect(0, 0, 0, 0);
    this.selectionRect = this.graphics.drawRect(0, 0, 0, 0);
    this.constraints = new Box(0, 0, w.layout.gridWidth, w.layout.gridHeight);
    this.zoomRect.node.classList.add("apexcharts-zoom-rect");
    this.selectionRect.node.classList.add("apexcharts-selection-rect");
    w.dom.Paper.add(this.zoomRect);
    w.dom.Paper.add(this.selectionRect);
    if (w.config.chart.selection.type === "x") {
      this.slDraggableRect = this.selectionRect.draggable({
        minX: 0,
        minY: 0,
        maxX: w.layout.gridWidth,
        maxY: w.layout.gridHeight
      }).on("dragmove.namespace", this.selectionDragging.bind(this, "dragging"));
    } else if (w.config.chart.selection.type === "y") {
      this.slDraggableRect = this.selectionRect.draggable({
        minX: 0,
        maxX: w.layout.gridWidth
      }).on("dragmove.namespace", this.selectionDragging.bind(this, "dragging"));
    } else {
      this.slDraggableRect = this.selectionRect.draggable().on("dragmove.namespace", this.selectionDragging.bind(this, "dragging"));
    }
    this.preselectedSelection();
    this.hoverArea = /** @type {Element} */
    w.dom.baseEl.querySelector(`${w.globals.chartClass} .apexcharts-svg`);
    if (!this.hoverArea) return;
    this.hoverArea.classList.add("apexcharts-zoomable");
    this.eventList.forEach((event) => {
      var _a;
      (_a = this.hoverArea) == null ? void 0 : _a.addEventListener(
        event,
        me.svgMouseEvents.bind(me, xyRatios),
        {
          capture: false,
          passive: true
        }
      );
    });
    if (this._wheelZoomEnabled()) {
      this.hoverArea.addEventListener("wheel", me.mouseWheelEvent.bind(me), {
        capture: false,
        passive: false
      });
    }
    if (this._momentumEnabled()) {
      ["touchstart", "touchmove", "touchend", "touchcancel"].forEach(
        (event) => {
          var _a;
          (_a = this.hoverArea) == null ? void 0 : _a.addEventListener(event, me.momentumTouch.bind(me), {
            capture: false,
            passive: false
          });
        }
      );
    }
  }
  // remove the event listeners which were previously added on hover area
  destroy() {
    if (this.slDraggableRect) {
      this.slDraggableRect.draggable(false);
      this.slDraggableRect.off();
      this.selectionRect.off();
    }
    this.selectionRect = null;
    this.zoomRect = null;
  }
  /**
   * @param {import('../types/internal').XYRatios} xyRatios
   * @param {any} e
   */
  svgMouseEvents(xyRatios, e) {
    const w = this.w;
    const toolbar = this.ctx.toolbar;
    if (w.interact.momentum && w.interact.momentum.busy) return;
    if (this._momentumEnabled() && e.touches && e.touches.length > 1) {
      return;
    }
    const zoomtype = w.interact.zoomEnabled ? w.config.chart.zoom.type : w.config.chart.selection.type;
    const autoSelected = w.config.chart.toolbar.autoSelected;
    if (autoSelected !== "measure") {
      if (e.shiftKey) {
        w.interact.shiftWasPressed = true;
        toolbar.enableZoomPanFromToolbar(autoSelected === "pan" ? "zoom" : "pan");
      } else {
        if (w.interact.shiftWasPressed) {
          toolbar.enableZoomPanFromToolbar(autoSelected);
          w.interact.shiftWasPressed = false;
        }
      }
    }
    if (!e.target) return;
    const tc = e.target.classList;
    let pc;
    if (e.target.parentNode && e.target.parentNode !== null) {
      pc = e.target.parentNode.classList;
    }
    const falsePositives = tc.contains("apexcharts-legend-marker") || tc.contains("apexcharts-legend-text") || pc && pc.contains("apexcharts-toolbar");
    if (falsePositives) return;
    this.clientX = e.type === "touchmove" || e.type === "touchstart" ? e.touches[0].clientX : e.type === "touchend" ? e.changedTouches[0].clientX : e.clientX;
    this.clientY = e.type === "touchmove" || e.type === "touchstart" ? e.touches[0].clientY : e.type === "touchend" ? e.changedTouches[0].clientY : e.clientY;
    if (e.type === "mousedown" && e.which === 1 || e.type === "touchstart") {
      const gridRectDim = this._gridRect();
      if (!gridRectDim) return;
      this.startX = this._screenXToPlotPx(this.clientX);
      this.startY = this.clientY - gridRectDim.top;
      this.dragged = false;
      this.w.interact.mousedown = true;
    }
    if (e.type === "mousemove" && e.which === 1 || e.type === "touchmove") {
      this.dragged = true;
      if (w.interact.panEnabled) {
        w.interact.selection = null;
        if (this.w.interact.mousedown) {
          this.panDragging({
            context: this,
            zoomtype,
            xyRatios: this.xyRatios
          });
        }
      } else {
        if (this.w.interact.mousedown && w.interact.zoomEnabled || this.w.interact.mousedown && w.interact.selectionEnabled) {
          this.selection = this.selectionDrawing({
            context: this,
            zoomtype
          });
        }
      }
    }
    if (e.type === "mouseup" || e.type === "touchend" || e.type === "mouseleave") {
      this.handleMouseUp({ zoomtype });
    }
    this.makeSelectionRectDraggable();
  }
  /** @param {{ zoomtype?: any, isResized?: any }} opts */
  handleMouseUp({ zoomtype, isResized }) {
    const w = this.w;
    const gridRectDim = this._gridRect();
    if (gridRectDim && (this.w.interact.mousedown || isResized)) {
      this.endX = this._screenXToPlotPx(this.clientX);
      this.endY = this.clientY - gridRectDim.top;
      this.dragX = Math.abs(this.endX - this.startX);
      this.dragY = Math.abs(this.endY - this.startY);
      if (w.interact.zoomEnabled || w.interact.selectionEnabled) {
        this.selectionDrawn({
          context: this,
          zoomtype
        });
      }
    }
    if (w.interact.zoomEnabled) {
      this.hideSelectionRect(this.selectionRect);
    }
    this.dragged = false;
    this.w.interact.mousedown = false;
  }
  // ---------------------------------------------------------------------------
  // Wheel zoom: continuous, cursor-anchored zoom on mouse wheel / trackpad.
  //
  // Each wheel event multiplies a pending zoom factor scaled to its deltaY (so
  // a trackpad's stream of tiny deltas and a discrete wheel's ±100 notches both
  // feel proportional), and the accumulated factor is applied at most once per
  // animation frame through the same immediate, animation-free fast path the
  // touch pinch uses (_applyXRange). Deliberately instant, trading-chart style:
  // no per-step morph and no easing between steps (an animated variant was
  // tried and rejected). The original implementation instead ran a fixed
  // 0.5x/1.5x animated update at most once per 400ms and dropped every wheel
  // event in between, which read as lag on continuous scrolling.
  //
  // Like Momentum (see the comment above momentumTouch), applying a frame
  // triggers _updateOptions, which destroys and recreates this instance
  // mid-gesture, so all wheel-gesture state lives on w.interact.wheel rather
  // than on the instance.
  // ---------------------------------------------------------------------------
  /**
   * A wheel or pinch zoom is an incidental gesture: the viewer can land in a
   * zoomed window without meaning to (a page scroll over the chart, a two-finger
   * swipe), so it is only offered when there is a way back out of it. The only
   * built-in way back is the toolbar's reset button, hence 'auto' (the default
   * for both allowMouseWheelZoom and pinch) resolves against that button being
   * present. A page that builds its own reset control sets the option to true
   * and gets the gesture with no toolbar. Drag-to-zoom is deliberate, so it is
   * not gated this way.
   *
   * @param {boolean|'auto'} setting
   */
  _incidentalZoomEnabled(setting) {
    var _a, _b, _c;
    const c = this.w.config.chart;
    if (!c.zoom || !c.zoom.enabled) return false;
    if (setting !== "auto") return !!setting;
    return !!(((_a = c.toolbar) == null ? void 0 : _a.show) && ((_c = (_b = c.toolbar) == null ? void 0 : _b.tools) == null ? void 0 : _c.reset));
  }
  _wheelZoomEnabled() {
    const { zoom } = this.w.config.chart;
    return this._incidentalZoomEnabled(zoom && zoom.allowMouseWheelZoom);
  }
  /** Lazily-created, re-render-surviving wheel-gesture state. */
  _wheel() {
    const it = this.w.interact;
    if (!it.wheel) {
      it.wheel = {
        factor: 1,
        clientX: 0,
        /** @type {number|null} */
        rafId: null,
        /** @type {any} */
        endTimer: null
      };
    }
    return it.wheel;
  }
  /**
   * @param {any} e
   */
  mouseWheelEvent(e) {
    e.preventDefault();
    const st = this._wheel();
    let dy = e.deltaY;
    if (e.deltaMode === 1) dy *= 33;
    else if (e.deltaMode === 2) dy *= 330;
    st.factor *= Math.pow(2, dy / WHEEL_ZOOM_PIXELS_PER_2X);
    st.clientX = e.clientX;
    if (st.rafId == null) {
      st.rafId = requestAnimationFrame(() => this._applyWheelZoom());
    }
    if (st.endTimer) clearTimeout(st.endTimer);
    st.endTimer = setTimeout(() => this._endWheelZoom(), 150);
  }
  /**
   * Apply the zoom factor accumulated since the last animation frame, keeping
   * the data value under the cursor pinned (both zooming in and out).
   */
  _applyWheelZoom() {
    const w = this.w;
    const st = this._wheel();
    st.rafId = null;
    const scale = st.factor;
    st.factor = 1;
    if (scale === 1 || w.globals.isDestroyed) return;
    const gridRectDim = this._gridRect();
    if (!gridRectDim || !gridRectDim.width) return;
    const { min, max } = this._currentXWindow();
    const range = max - min;
    const mouseX = Math.min(
      Math.max((st.clientX - gridRectDim.left) / gridRectDim.width, 0),
      1
    );
    let newRange = range * scale;
    const bounds = this._clampBounds();
    if (bounds) {
      const minXDiff = w.globals.minXDiff > 0 && isFinite(w.globals.minXDiff) ? w.globals.minXDiff : 0;
      const minRange = Math.max(minXDiff * 2, (bounds.max - bounds.min) * 1e-6);
      if (newRange < minRange) newRange = minRange;
      if (newRange > bounds.max - bounds.min) newRange = bounds.max - bounds.min;
    }
    const anchor = min + mouseX * range;
    let newMinX = anchor - mouseX * newRange;
    let newMaxX = newMinX + newRange;
    const eps = range * 1e-9;
    if (Math.abs(newMinX - min) < eps && Math.abs(newMaxX - max) < eps) return;
    if (isNaN(newMinX) || isNaN(newMaxX)) return;
    const beforeZoomRange = this.getBeforeZoomRange(
      { min: newMinX, max: newMaxX },
      /** @type {any} */
      void 0
    );
    if (beforeZoomRange && beforeZoomRange.xaxis) {
      newMinX = beforeZoomRange.xaxis.min;
      newMaxX = beforeZoomRange.xaxis.max;
    }
    this._applyXRange(newMinX, newMaxX, true);
  }
  /** Fire the zoomed callback once the wheel gesture settles (mirrors _endPinch). */
  _endWheelZoom() {
    const w = this.w;
    const st = this._wheel();
    st.endTimer = null;
    if (w.globals.isDestroyed || !w.interact.zoomed) return;
    const { min, max } = this._currentXWindow();
    const yaxis = w.globals.initialConfig ? Utils.clone(w.globals.initialConfig.yaxis) : [];
    const toolbar = this.ctx.toolbar;
    if (toolbar) toolbar.zoomCallback({ min, max }, yaxis);
  }
  makeSelectionRectDraggable() {
    const w = this.w;
    if (!this.selectionRect) return;
    const rectDim = this.selectionRect.node.getBoundingClientRect();
    if (rectDim.width > 0 && rectDim.height > 0) {
      this.selectionRect.select(false).resize(false);
      this.selectionRect.select({
        createRot: () => {
        },
        updateRot: () => {
        },
        createHandle: (group, p, index, pointArr, handleName) => {
          if (handleName === "l" || handleName === "r")
            return group.circle(8).css({ "stroke-width": 1, stroke: "#333", fill: "#fff" });
          return group.circle(0);
        },
        updateHandle: (group, p) => {
          return group.center(p[0], p[1]);
        }
      }).resize().on("resize", () => {
        var _a;
        if (w.interact.selectionEnabled) {
          w.interact.selection = {
            x: parseFloat(this.selectionRect.node.getAttribute("x")),
            y: parseFloat(this.selectionRect.node.getAttribute("y")),
            width: parseFloat(this.selectionRect.node.getAttribute("width")),
            height: parseFloat(this.selectionRect.node.getAttribute("height"))
          };
          clearTimeout((_a = this.w.globals.selectionResizeTimer) != null ? _a : void 0);
          this.w.globals.selectionResizeTimer = window.setTimeout(() => {
            this._emitSelectionFromRect();
          }, 30);
        } else {
          const zoomtype = w.interact.zoomEnabled ? w.config.chart.zoom.type : w.config.chart.selection.type;
          this.handleMouseUp({ zoomtype, isResized: true });
        }
      });
    }
  }
  preselectedSelection() {
    const w = this.w;
    const xyRatios = this.xyRatios;
    if (!w.interact.zoomEnabled) {
      if (typeof w.interact.selection !== "undefined" && w.interact.selection !== null) {
        this.drawSelectionRect(__spreadProps(__spreadValues({}, w.interact.selection), {
          translateX: w.layout.translateX,
          translateY: w.layout.translateY
        }));
      } else {
        if (w.config.chart.selection.xaxis.min !== void 0 && w.config.chart.selection.xaxis.max !== void 0) {
          let x = AxisMapping.dataXToPx(w, w.config.chart.selection.xaxis.min);
          let width = AxisMapping.dataXToPx(w, w.config.chart.selection.xaxis.max) - x;
          if (w.axisFlags.isRangeBar) {
            x = (w.config.chart.selection.xaxis.min - w.globals.yAxisScale[0].niceMin) / xyRatios.invertedYRatio;
            width = (w.config.chart.selection.xaxis.max - w.config.chart.selection.xaxis.min) / xyRatios.invertedYRatio;
          }
          const selectionRect = {
            x,
            y: 0,
            width,
            height: w.layout.gridHeight,
            translateX: w.layout.translateX,
            translateY: w.layout.translateY,
            selectionEnabled: true
          };
          this.drawSelectionRect(selectionRect);
          this.makeSelectionRectDraggable();
          if (typeof w.config.chart.events.selection === "function") {
            w.config.chart.events.selection(this.ctx, {
              xaxis: {
                min: w.config.chart.selection.xaxis.min,
                max: w.config.chart.selection.xaxis.max
              },
              yaxis: {}
            });
          }
        }
      }
    }
  }
  /** @param {{x: any, y: any, width: any, height: any, translateX: any, translateY: any}} opts */
  drawSelectionRect({ x, y, width, height, translateX = 0, translateY = 0 }) {
    const w = this.w;
    const zoomRect = this.zoomRect;
    const selectionRect = this.selectionRect;
    if (this.dragged || w.interact.selection !== null) {
      const scalingAttrs = {
        transform: "translate(" + translateX + ", " + translateY + ")"
      };
      if (w.interact.zoomEnabled && this.dragged) {
        if (width < 0) width = 1;
        zoomRect.attr({
          x,
          y,
          width,
          height,
          fill: w.config.chart.zoom.zoomedArea.fill.color,
          "fill-opacity": w.config.chart.zoom.zoomedArea.fill.opacity,
          stroke: w.config.chart.zoom.zoomedArea.stroke.color,
          "stroke-width": w.config.chart.zoom.zoomedArea.stroke.width,
          "stroke-opacity": w.config.chart.zoom.zoomedArea.stroke.opacity
        });
        Graphics.setAttrs(zoomRect.node, scalingAttrs);
      }
      if (w.interact.selectionEnabled) {
        selectionRect.attr({
          x,
          y,
          width: width > 0 ? width : 0,
          height: height > 0 ? height : 0,
          fill: w.config.chart.selection.fill.color,
          "fill-opacity": w.config.chart.selection.fill.opacity,
          stroke: w.config.chart.selection.stroke.color,
          "stroke-width": w.config.chart.selection.stroke.width,
          "stroke-dasharray": w.config.chart.selection.stroke.dashArray,
          "stroke-opacity": w.config.chart.selection.stroke.opacity
        });
        Graphics.setAttrs(selectionRect.node, scalingAttrs);
      }
    }
  }
  /**
   * @param {any} rect
   */
  hideSelectionRect(rect) {
    if (rect) {
      rect.attr({
        x: 0,
        y: 0,
        width: 0,
        height: 0
      });
    }
  }
  selectionDrawing({ context, zoomtype }) {
    const w = this.w;
    const me = context;
    const gridRectDim = this._gridRect();
    if (!gridRectDim) return;
    const startX = me.startX - 1;
    const startY = me.startY;
    let inversedX = false;
    let inversedY = false;
    const left = this._screenXToPlotPx(me.clientX);
    const top = me.clientY - gridRectDim.top;
    let selectionWidth = left - startX;
    let selectionHeight = top - startY;
    let selectionRect = {
      translateX: w.layout.translateX,
      translateY: w.layout.translateY
    };
    if (Math.abs(selectionWidth + startX) > w.layout.gridWidth) {
      selectionWidth = w.layout.gridWidth - startX;
    } else if (left < 0) {
      selectionWidth = startX;
    }
    if (startX > left) {
      inversedX = true;
      selectionWidth = Math.abs(selectionWidth);
    }
    if (startY > top) {
      inversedY = true;
      selectionHeight = Math.abs(selectionHeight);
    }
    if (zoomtype === "x") {
      selectionRect = {
        x: inversedX ? startX - selectionWidth : startX,
        y: 0,
        width: selectionWidth,
        height: w.layout.gridHeight
      };
    } else if (zoomtype === "y") {
      selectionRect = {
        x: 0,
        y: inversedY ? startY - selectionHeight : startY,
        width: w.layout.gridWidth,
        height: selectionHeight
      };
    } else {
      selectionRect = {
        x: inversedX ? startX - selectionWidth : startX,
        y: inversedY ? startY - selectionHeight : startY,
        width: selectionWidth,
        height: selectionHeight
      };
    }
    selectionRect = __spreadProps(__spreadValues({}, selectionRect), {
      translateX: w.layout.translateX,
      translateY: w.layout.translateY
    });
    me.drawSelectionRect(selectionRect);
    me.selectionDragging("resizing");
    return selectionRect;
  }
  /**
   * @param {string} type
   * @param {CustomEvent} e
   */
  selectionDragging(type, e) {
    var _a;
    const w = this.w;
    if (!e) return;
    e.preventDefault();
    const { handler, box } = e.detail;
    const constraints = (
      /** @type {any} */
      this.constraints
    );
    let { x, y } = box;
    if (x < constraints.x) {
      x = constraints.x;
    }
    if (y < constraints.y) {
      y = constraints.y;
    }
    if (box.x2 > constraints.x2) {
      x = constraints.x2 - box.w;
    }
    if (box.y2 > constraints.y2) {
      y = constraints.y2 - box.h;
    }
    handler.move(x, y);
    const selRect = this.selectionRect;
    let timerInterval = 0;
    if (type === "resizing") {
      timerInterval = 30;
    }
    const getSelAttr = (attr) => {
      return parseFloat(selRect.node.getAttribute(attr));
    };
    const draggedProps = {
      x: getSelAttr("x"),
      y: getSelAttr("y"),
      width: getSelAttr("width"),
      height: getSelAttr("height")
    };
    w.interact.selection = draggedProps;
    const link = w.config.chart.link;
    const linkActive = !!(link && (link.enabled || typeof link.dimension === "function"));
    if ((typeof w.config.chart.events.selection === "function" || linkActive) && w.interact.selectionEnabled) {
      clearTimeout((_a = this.w.globals.selectionResizeTimer) != null ? _a : void 0);
      this.w.globals.selectionResizeTimer = window.setTimeout(() => {
        this._emitSelectionFromRect();
      }, timerInterval);
    }
  }
  /**
   * Recompute the reported x/y range from the CURRENT persistent selection rect
   * (via the shared AxisMapping) and notify listeners: chart.events.selection,
   * brushScrolled, and the crossfilter coordinator. Shared by the rect-body drag
   * (selectionDragging) and the handle resize (makeSelectionRectDraggable) so
   * every gesture re-reports through ONE mapping and the reported range always
   * matches the rect the user sees. No dragged/threshold gate: reaching here
   * already means the user moved or resized the persistent rect.
   */
  _emitSelectionFromRect() {
    var _a;
    const w = this.w;
    if (!w.interact.selectionEnabled) return;
    const link = w.config.chart.link;
    const linkActive = !!(link && (link.enabled || typeof link.dimension === "function"));
    if (typeof w.config.chart.events.selection !== "function" && !linkActive) {
      return;
    }
    const gridRectDim = this._gridRect();
    if (!gridRectDim) return;
    const selectionRect = this.selectionRect.node.getBoundingClientRect();
    const xyRatios = this.xyRatios;
    let minX, maxX, minY, maxY;
    const relLeft = this._screenXToPlotPx(selectionRect.left);
    const relRight = this._screenXToPlotPx(selectionRect.right);
    if (!w.axisFlags.isRangeBar) {
      if (!w.globals.xAxisScale) return;
      minX = AxisMapping.pxToDataX(w, relLeft);
      maxX = AxisMapping.pxToDataX(w, relRight);
      minY = w.globals.yAxisScale[0].niceMin + (gridRectDim.bottom - selectionRect.bottom) * xyRatios.yRatio[0];
      maxY = w.globals.yAxisScale[0].niceMax - (selectionRect.top - gridRectDim.top) * xyRatios.yRatio[0];
    } else {
      minX = w.globals.yAxisScale[0].niceMin + relLeft * xyRatios.invertedYRatio;
      maxX = w.globals.yAxisScale[0].niceMin + relRight * xyRatios.invertedYRatio;
      minY = 0;
      maxY = 1;
    }
    const xyAxis = {
      xaxis: { min: minX, max: maxX },
      yaxis: { min: minY, max: maxY }
    };
    if (typeof w.config.chart.events.selection === "function") {
      w.config.chart.events.selection(this.ctx, xyAxis);
    }
    if (w.config.chart.brush.enabled && w.config.chart.events.brushScrolled !== void 0) {
      w.config.chart.events.brushScrolled(this.ctx, xyAxis);
    }
    (_a = this.ctx.linkedViews) == null ? void 0 : _a.onSourceSelection(xyAxis.xaxis);
  }
  /** @param {{context: any, zoomtype: any}} opts */
  selectionDrawn({ context, zoomtype }) {
    var _a;
    const w = this.w;
    const me = context;
    const xyRatios = this.xyRatios;
    const toolbar = this.ctx.toolbar;
    const selRect = w.interact.zoomEnabled ? me.zoomRect.node.getBoundingClientRect() : me.selectionRect.node.getBoundingClientRect();
    const gridRectDim = me._gridRect();
    if (!gridRectDim) return;
    const localStartX = this._screenXToPlotPx(selRect.left);
    const localEndX = this._screenXToPlotPx(selRect.right);
    const localStartY = selRect.top - gridRectDim.top;
    const localEndY = selRect.bottom - gridRectDim.top;
    let xLowestValue, xHighestValue;
    if (!w.axisFlags.isRangeBar) {
      xLowestValue = AxisMapping.pxToDataX(w, localStartX);
      xHighestValue = AxisMapping.pxToDataX(w, localEndX);
    } else {
      xLowestValue = w.globals.yAxisScale[0].niceMin + localStartX * xyRatios.invertedYRatio;
      xHighestValue = w.globals.yAxisScale[0].niceMin + localEndX * xyRatios.invertedYRatio;
    }
    const yHighestValue = [];
    const yLowestValue = [];
    w.config.yaxis.forEach((yaxe, index) => {
      const seriesIndex = w.globals.seriesYAxisMap[index][0];
      const highestVal = w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[seriesIndex] * localStartY;
      const lowestVal = w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[seriesIndex] * localEndY;
      yHighestValue.push(highestVal);
      yLowestValue.push(lowestVal);
    });
    if (me.dragged && (me.dragX > 10 || me.dragY > 10) && xLowestValue !== xHighestValue) {
      if (w.interact.zoomEnabled) {
        if (!w.globals.initialConfig) return;
        let yaxis = Utils.clone(w.globals.initialConfig.yaxis);
        let xaxis = Utils.clone(w.globals.initialConfig.xaxis);
        w.interact.zoomed = true;
        if (w.config.xaxis.convertedCatToNumeric) {
          xLowestValue = Math.floor(xLowestValue);
          xHighestValue = Math.floor(xHighestValue);
          if (xLowestValue < 1) {
            xLowestValue = 1;
            xHighestValue = w.globals.dataPoints;
          }
          if (xHighestValue - xLowestValue < 2) {
            xHighestValue = xLowestValue + 1;
          }
        }
        if (zoomtype === "xy" || zoomtype === "x") {
          xaxis = {
            min: xLowestValue,
            max: xHighestValue
          };
        }
        if (zoomtype === "xy" || zoomtype === "y") {
          yaxis.forEach((yaxe, index) => {
            yaxis[index].min = yLowestValue[index];
            yaxis[index].max = yHighestValue[index];
          });
        }
        if (toolbar) {
          const beforeZoomRange = toolbar.getBeforeZoomRange(xaxis, yaxis);
          if (beforeZoomRange) {
            xaxis = beforeZoomRange.xaxis ? beforeZoomRange.xaxis : xaxis;
            yaxis = beforeZoomRange.yaxis ? beforeZoomRange.yaxis : yaxis;
          }
        }
        const options = {
          xaxis
        };
        if (!w.config.chart.group) {
          options.yaxis = yaxis;
        }
        me.ctx.updateHelpers._updateOptions(
          options,
          false,
          me.w.config.chart.animations.dynamicAnimation.enabled
        );
        if (typeof w.config.chart.events.zoomed === "function") {
          toolbar.zoomCallback(xaxis, yaxis);
        }
      } else if (w.interact.selectionEnabled) {
        let yaxis = null;
        let xaxis = null;
        xaxis = {
          min: xLowestValue,
          max: xHighestValue
        };
        if (zoomtype === "xy" || zoomtype === "y") {
          const yaxisCopy = (
            /** @type {ApexYAxis[]} */
            Utils.clone(w.config.yaxis)
          );
          yaxis = yaxisCopy;
          yaxisCopy.forEach((yaxe, index) => {
            yaxisCopy[index].min = yLowestValue[index];
            yaxisCopy[index].max = yHighestValue[index];
          });
        }
        w.interact.selection = me.selection;
        if (typeof w.config.chart.events.selection === "function") {
          w.config.chart.events.selection(me.ctx, {
            xaxis,
            yaxis
          });
        }
        (_a = me.ctx.linkedViews) == null ? void 0 : _a.onSourceSelection(xaxis);
      }
    }
  }
  /** @param {{ context?: any, zoomtype?: any, xyRatios?: any }} opts */
  panDragging({ context }) {
    var _a;
    const w = this.w;
    const me = context;
    if (typeof w.interact.lastClientPosition.x !== "undefined") {
      const deltaX = w.interact.lastClientPosition.x - me.clientX;
      const deltaY = ((_a = w.interact.lastClientPosition.y) != null ? _a : 0) - me.clientY;
      if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
        this.moveDirection = "left";
      } else if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
        this.moveDirection = "right";
      } else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) {
        this.moveDirection = "up";
      } else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) {
        this.moveDirection = "down";
      }
    }
    w.interact.lastClientPosition = {
      x: me.clientX,
      y: me.clientY
    };
    const xLowestValue = w.axisFlags.isRangeBar ? w.globals.minY : w.globals.minX;
    const xHighestValue = w.axisFlags.isRangeBar ? w.globals.maxY : w.globals.maxX;
    me.panScrolled(xLowestValue, xHighestValue);
  }
  // delayedPanScrolled() {
  //   const w = this.w
  //   let newMinX = w.globals.minX
  //   let newMaxX = w.globals.maxX
  //   const centerX = (w.globals.maxX - w.globals.minX) / 2
  //   if (this.moveDirection === 'left') {
  //     newMinX = w.globals.minX + centerX
  //     newMaxX = w.globals.maxX + centerX
  //   } else if (this.moveDirection === 'right') {
  //     newMinX = w.globals.minX - centerX
  //     newMaxX = w.globals.maxX - centerX
  //   }
  //   newMinX = Math.floor(newMinX)
  //   newMaxX = Math.floor(newMaxX)
  //   this.updateScrolledChart(
  //     { xaxis: { min: newMinX, max: newMaxX } },
  //     newMinX,
  //     newMaxX
  //   )
  // }
  /**
   * @param {number} xLowestValue
   * @param {number} xHighestValue
   */
  panScrolled(xLowestValue, xHighestValue) {
    var _a, _b;
    const w = this.w;
    const xyRatios = this.xyRatios;
    if (!w.globals.initialConfig) return;
    const yaxis = Utils.clone(w.globals.initialConfig.yaxis);
    let xRatio = xyRatios.xRatio;
    let minX = w.globals.minX;
    let maxX = w.globals.maxX;
    if (w.axisFlags.isRangeBar) {
      xRatio = xyRatios.invertedYRatio;
      minX = w.globals.minY;
      maxX = w.globals.maxY;
    }
    if (this.moveDirection === "left") {
      xLowestValue = minX + w.layout.gridWidth / PAN_NUDGE_DIVISOR * xRatio;
      xHighestValue = maxX + w.layout.gridWidth / PAN_NUDGE_DIVISOR * xRatio;
    } else if (this.moveDirection === "right") {
      xLowestValue = minX - w.layout.gridWidth / PAN_NUDGE_DIVISOR * xRatio;
      xHighestValue = maxX - w.layout.gridWidth / PAN_NUDGE_DIVISOR * xRatio;
    }
    if (!w.axisFlags.isRangeBar) {
      const clampMin = (_a = w.globals.dataReducerRawMinX) != null ? _a : w.globals.initialMinX;
      const clampMax = (_b = w.globals.dataReducerRawMaxX) != null ? _b : w.globals.initialMaxX;
      if (xLowestValue < clampMin || xHighestValue > clampMax) {
        xLowestValue = minX;
        xHighestValue = maxX;
      }
    }
    const xaxis = {
      min: xLowestValue,
      max: xHighestValue
    };
    const options = {
      xaxis
    };
    if (!w.config.chart.group) {
      options.yaxis = yaxis;
    }
    this.updateScrolledChart(options, xLowestValue, xHighestValue);
  }
  /**
   * @param {object} options
   * @param {number} xLowestValue
   * @param {number} xHighestValue
   */
  updateScrolledChart(options, xLowestValue, xHighestValue) {
    const w = this.w;
    this.ctx.updateHelpers._updateOptions(options, false, false);
    if (typeof w.config.chart.events.scrolled === "function") {
      const args = {
        xaxis: {
          min: xLowestValue,
          max: xHighestValue
        }
      };
      w.config.chart.events.scrolled(this.ctx, args);
      this.ctx.events.fireEvent("scrolled", args);
    }
  }
  // ---------------------------------------------------------------------------
  // Momentum: multi-touch pinch-zoom, two-finger pan and kinetic inertia.
  //
  // Every _updateOptions destroys and recreates this instance, and applying a
  // gesture frame IS an _updateOptions, so the gesture must not depend on the
  // instance surviving. All runtime state lives on w.interact.momentum (the
  // interaction slice that persists across re-renders, like the crude pan's
  // lastClientPosition). The instance that received touchstart keeps driving
  // the gesture off the persistent state; inertia is a self-contained rAF loop
  // that stops on w.globals.isDestroyed (a real destroy) rather than being
  // cancelled by the per-update destroy().
  // ---------------------------------------------------------------------------
  _momentumEnabled() {
    return this._pinchEnabled() || this._panInertiaEnabled();
  }
  _pinchEnabled() {
    return this._incidentalZoomEnabled(this.w.config.chart.zoom.pinch);
  }
  _panInertiaEnabled() {
    const c = this.w.config.chart;
    return !!(c.pan && c.pan.inertia);
  }
  /** Lazily-created, re-render-surviving gesture state on the interaction slice. */
  _m() {
    const it = this.w.interact;
    if (!it.momentum) {
      it.momentum = {
        busy: false,
        /** @type {any} */
        pinch: null,
        /** @type {any} */
        panState: null,
        /** @type {{x:number,t:number}[]} */
        samples: [],
        /** @type {number|null} */
        inertiaRAF: null
      };
    }
    return it.momentum;
  }
  /** Current x data-window (rangeBars carry the datetime domain on y). */
  _currentXWindow() {
    const w = this.w;
    return w.axisFlags.isRangeBar ? { min: w.globals.minY, max: w.globals.maxY } : { min: w.globals.minX, max: w.globals.maxX };
  }
  /** Live grid rect from the current DOM. Never cache the grid node on the
   * instance: a full render replaces this whole instance, but the fast update
   * path (fastUpdate/_fastAxisChromeRefresh) keeps the instance while swapping
   * the grid node, and a cached node would go stale (detached nodes report an
   * all-zero bounding rect, silently corrupting selection geometry). */
  _gridRect() {
    const baseEl = this.w.dom.baseEl;
    const grid = baseEl && baseEl.querySelector(".apexcharts-grid");
    return grid ? grid.getBoundingClientRect() : null;
  }
  /**
   * Convert an absolute (client) x pixel to the plot-origin coordinate space
   * that bar placement and the selection rect transform both use:
   * `screenX - svgLeft - translateX`. This is the ONLY correct reference for the
   * numeric/datetime x mapping (see AxisMapping): do NOT measure from the
   * `.apexcharts-grid` box and subtract barPadForNumericAxis, because on a
   * numeric bar chart that box extends barPad to the LEFT of the plot origin, so
   * the two corrections are a fragile pair that only cancels while the grid box
   * happens to extend exactly barPad. Anchoring on translateX (the same origin
   * the bars use) is stable regardless of grid padding.
   * @param {number} screenX
   * @returns {number}
   */
  _screenXToPlotPx(screenX) {
    return AxisMapping.screenXToPlotPx(this.w, screenX);
  }
  /**
   * Raw data bounds to clamp against. When zoom-aware downsampling is active,
   * the raw stash tracks the full domain; fall back to the initial window.
   * Returns null for rangeBars (no raw-x clamp available).
   * @returns {{min:number, max:number}|null}
   */
  _clampBounds() {
    var _a, _b;
    const w = this.w;
    if (w.axisFlags.isRangeBar) return null;
    return {
      min: (_a = w.globals.dataReducerRawMinX) != null ? _a : w.globals.initialMinX,
      max: (_b = w.globals.dataReducerRawMaxX) != null ? _b : w.globals.initialMaxX
    };
  }
  /**
   * Apply an x-window immediately (no animation), mirroring panScrolled but
   * pixel-accurate: clamp to the raw bounds (preserving window width so a pan
   * stops flush at the edge rather than shrinking), floor for category axes,
   * then route through the fast _updateOptions path.
   * @param {number} newMinX @param {number} newMaxX @param {boolean} isZoom
   * @returns {{minX:number, maxX:number}|false} applied window, or false if rejected
   */
  _applyXRange(newMinX, newMaxX, isZoom) {
    const w = this.w;
    if (!w.globals.initialConfig) return false;
    const cur = this._currentXWindow();
    const zoomingOut = isZoom && newMaxX - newMinX > cur.max - cur.min;
    const bounds = this._clampBounds();
    if (bounds) {
      const range = newMaxX - newMinX;
      if (newMinX < bounds.min) {
        newMinX = bounds.min;
        newMaxX = newMinX + range;
      }
      if (newMaxX > bounds.max) {
        newMaxX = bounds.max;
        newMinX = newMaxX - range;
      }
      if (newMinX < bounds.min) newMinX = bounds.min;
    }
    if (w.config.xaxis.convertedCatToNumeric) {
      newMinX = Math.floor(newMinX);
      newMaxX = zoomingOut ? Math.ceil(newMaxX) : Math.floor(newMaxX);
      if (newMinX < 1) newMinX = 1;
      if (bounds && newMaxX > bounds.max) newMaxX = Math.floor(bounds.max);
      if (newMaxX - newMinX < 2) return false;
    }
    if (!(newMaxX > newMinX)) return false;
    const options = { xaxis: { min: newMinX, max: newMaxX } };
    if (!w.config.chart.group) {
      options.yaxis = Utils.clone(w.globals.initialConfig.yaxis);
    }
    if (isZoom) w.interact.zoomed = true;
    this.ctx.updateHelpers._updateOptions(options, false, false);
    return { minX: newMinX, maxX: newMaxX };
  }
  _cancelInertia() {
    const m = this._m();
    if (m.inertiaRAF != null) {
      cancelAnimationFrame(m.inertiaRAF);
      m.inertiaRAF = null;
    }
  }
  _fireScrolled() {
    const w = this.w;
    if (typeof w.config.chart.events.scrolled !== "function") return;
    const { min, max } = this._currentXWindow();
    const args = { xaxis: { min, max } };
    w.config.chart.events.scrolled(this.ctx, args);
    this.ctx.events.fireEvent("scrolled", args);
  }
  /** @param {number} x @param {number} t */
  _pushSample(x, t) {
    const s = this._m().samples;
    s.push({ x, t });
    while (s.length > 6) s.shift();
  }
  /**
   * Single passive:false handler for all touch phases. Two fingers => pinch /
   * two-finger pan (zoom). One finger, in pan mode => kinetic pan with inertia.
   * @param {any} e
   */
  momentumTouch(e) {
    const w = this.w;
    const m = this._m();
    const type = e.type;
    if (type === "touchstart") {
      this._cancelInertia();
      const gridRectDim = this._gridRect();
      if (!gridRectDim) return;
      if (e.touches.length >= 2 && this._pinchEnabled()) {
        e.preventDefault();
        m.busy = true;
        m.panState = null;
        this._beginPinch(e, gridRectDim);
      } else if (e.touches.length === 1 && this._panInertiaEnabled() && w.interact.panEnabled) {
        m.busy = true;
        m.pinch = null;
        const t = e.touches[0];
        const win = this._currentXWindow();
        const gw = w.layout.gridWidth || 1;
        m.panState = {
          startX: t.clientX,
          startY: t.clientY,
          axis: null,
          // decided on first move (rails)
          minX0: win.min,
          maxX0: win.max,
          ratio0: (win.max - win.min) / gw
        };
        m.samples = [{ x: t.clientX, t: e.timeStamp }];
      }
      return;
    }
    if (type === "touchmove") {
      if (m.pinch && e.touches.length >= 2) {
        e.preventDefault();
        this._movePinch(e);
      } else if (m.panState && e.touches.length === 1) {
        this._movePan(e);
      }
      return;
    }
    if (m.pinch) {
      if (e.touches.length < 2) this._endPinch();
    } else if (m.panState) {
      if (e.touches.length === 0) this._endPan();
    }
    if (e.touches.length === 0) {
      w.interact.mousedown = false;
      this.dragged = false;
      if (m.inertiaRAF == null && !m.pinch && !m.panState) {
        m.busy = false;
      }
    }
  }
  /** @param {any} e @param {DOMRect} gridRectDim */
  _beginPinch(e, gridRectDim) {
    const w = this.w;
    const t0 = e.touches[0];
    const t1 = e.touches[1];
    const dist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY) || 1;
    const cx = (t0.clientX + t1.clientX) / 2 - gridRectDim.left - w.globals.barPadForNumericAxis;
    const { min, max } = this._currentXWindow();
    this._m().pinch = {
      d0: dist,
      cx0: cx,
      minX0: min,
      maxX0: max,
      gridWidth: w.layout.gridWidth || 1
    };
  }
  /** @param {any} e */
  _movePinch(e) {
    const w = this.w;
    const p = this._m().pinch;
    if (!p) return;
    const gridRectDim = this._gridRect();
    if (!gridRectDim) return;
    const t0 = e.touches[0];
    const t1 = e.touches[1];
    const dist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY) || 1;
    const cx = (t0.clientX + t1.clientX) / 2 - gridRectDim.left - w.globals.barPadForNumericAxis;
    const range0 = p.maxX0 - p.minX0;
    const newRange = range0 * (p.d0 / dist);
    const anchorData = p.minX0 + p.cx0 / p.gridWidth * range0;
    let newMinX = anchorData - cx / p.gridWidth * newRange;
    let newMaxX = newMinX + newRange;
    const bounds = this._clampBounds();
    if (bounds) {
      const minXDiff = w.globals.minXDiff > 0 && isFinite(w.globals.minXDiff) ? w.globals.minXDiff : 0;
      const minRange = Math.max(minXDiff * 2, (bounds.max - bounds.min) * 1e-6);
      if (newMaxX - newMinX < minRange) {
        const mid = (newMinX + newMaxX) / 2;
        newMinX = mid - minRange / 2;
        newMaxX = mid + minRange / 2;
      }
    }
    this._applyXRange(newMinX, newMaxX, true);
  }
  _endPinch() {
    const w = this.w;
    const m = this._m();
    m.pinch = null;
    const { min, max } = this._currentXWindow();
    const xaxis = { min, max };
    const yaxis = w.globals.initialConfig ? Utils.clone(w.globals.initialConfig.yaxis) : [];
    const toolbar = this.ctx.toolbar;
    if (toolbar) toolbar.zoomCallback(xaxis, yaxis);
  }
  /** @param {any} e */
  _movePan(e) {
    const m = this._m();
    const s = m.panState;
    const t = e.touches[0];
    if (!s.axis) {
      const dx = Math.abs(t.clientX - s.startX);
      const dy = Math.abs(t.clientY - s.startY);
      if (dx < 6 && dy < 6) {
        this._pushSample(t.clientX, e.timeStamp);
        return;
      }
      if (dy > dx) {
        m.busy = false;
        m.panState = null;
        return;
      }
      s.axis = "x";
    }
    if (s.axis !== "x") return;
    e.preventDefault();
    const totalDeltaPx = t.clientX - s.startX;
    const deltaData = totalDeltaPx * s.ratio0;
    this._pushSample(t.clientX, e.timeStamp);
    this._applyXRange(s.minX0 - deltaData, s.maxX0 - deltaData, false);
  }
  _endPan() {
    const m = this._m();
    const s = m.panState;
    m.panState = null;
    let vel = 0;
    const samples = m.samples;
    if (samples.length >= 2) {
      const a = samples[0];
      const b = samples[samples.length - 1];
      const dt = b.t - a.t;
      if (dt > 0) vel = (b.x - a.x) / dt;
    }
    m.samples = [];
    if (s && s.axis === "x" && this._panInertiaEnabled() && Math.abs(vel) > INERTIA_MIN_RELEASE_VELOCITY) {
      this._startInertia(vel);
    } else {
      m.busy = false;
      this._fireScrolled();
    }
  }
  /**
   * Kinetic glide after a one-finger pan release: decay the velocity by
   * `friction` each frame and shift the window, stopping at the data edge
   * (clamp, not elastic overshoot). The loop is w-driven, so it keeps running
   * across the re-renders each frame triggers and stops only on a real destroy.
   * @param {number} vel0 px/ms, sign is the finger direction
   */
  _startInertia(vel0) {
    const w = this.w;
    const m = this._m();
    const cfgFriction = w.config.chart.pan && w.config.chart.pan.friction;
    const friction = typeof cfgFriction === "number" ? Math.min(Math.max(cfgFriction, 0.5), 0.999) : INERTIA_DEFAULT_FRICTION;
    let vel = vel0;
    let lastT = null;
    m.busy = true;
    const step = (ts) => {
      if (w.globals.isDestroyed) {
        m.inertiaRAF = null;
        m.busy = false;
        return;
      }
      if (lastT == null) {
        lastT = ts;
        m.inertiaRAF = requestAnimationFrame(step);
        return;
      }
      const dt = ts - lastT;
      lastT = ts;
      vel *= Math.pow(friction, dt / FRAME_MS_60FPS);
      if (Math.abs(vel) < INERTIA_STOP_VELOCITY) {
        m.inertiaRAF = null;
        m.busy = false;
        this._fireScrolled();
        return;
      }
      const win = this._currentXWindow();
      const gw = w.layout.gridWidth || 1;
      const ratio = (win.max - win.min) / gw;
      const deltaData = vel * dt * ratio;
      const applied = this._applyXRange(
        win.min - deltaData,
        win.max - deltaData,
        false
      );
      const bounds = this._clampBounds();
      const hitEdge = !applied || bounds && (deltaData > 0 && applied.minX <= bounds.min + (bounds.max - bounds.min) * 1e-6 || deltaData < 0 && applied.maxX >= bounds.max - (bounds.max - bounds.min) * 1e-6);
      if (hitEdge) {
        m.inertiaRAF = null;
        m.busy = false;
        this._fireScrolled();
        return;
      }
      m.inertiaRAF = requestAnimationFrame(step);
    };
    m.inertiaRAF = requestAnimationFrame(step);
  }
}
_core__default.registerFeatures({
  toolbar: Toolbar,
  zoomPanSelection: ZoomPanSelection
});
export {
  default2 as default
};