apexcharts
Version:
A JavaScript Chart Library
1,048 lines (1,047 loc) • 39.1 kB
JavaScript
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 = (a2, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a2, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a2, prop, b[prop]);
}
return a2;
};
var __spreadProps = (a2, b) => __defProps(a2, __getOwnPropDescs(b));
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e2) {
reject(e2);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e2) {
reject(e2);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
/*!
* 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 Environment = _core.__apex_Environment_Environment;
const prefersReducedMotion = _core.__apex_Animations_prefersReducedMotion;
const e = globalThis.console;
function t(t2) {
e.error(t2);
}
function s(t2) {
e.warn(t2);
}
const i = "APEX-", n = /* @__PURE__ */ new Date("2027-07-31T00:00:00Z"), r = "__apex_license_v1__";
function a() {
const e2 = globalThis;
let t2 = e2[r];
return t2 || (t2 = { key: null, listeners: /* @__PURE__ */ new Set(), result: null }, e2[r] = t2), t2;
}
const l = class {
static get licenseKey() {
return a().key;
}
static set licenseKey(e2) {
a().key = e2;
}
static get listeners() {
return a().listeners;
}
static get validationResult() {
return a().result;
}
static set validationResult(e2) {
a().result = e2;
}
static getKey() {
return this.licenseKey;
}
static getLicenseStatus() {
return this.licenseKey ? (this.validationResult = this.validateKey(this.licenseKey), this.validationResult) : { expired: false, signatureVerified: false, valid: false };
}
static isKeyValid(e2) {
return !!e2 && this.validateKey(e2).valid;
}
static isLicenseValid() {
return this.getLicenseStatus().valid;
}
static onChange(e2) {
return this.listeners.add(e2), () => {
this.listeners.delete(e2);
};
}
static setLicense(e2) {
var _a;
var i2;
if (!e2) return this.licenseKey = null, void this.publish({ expired: false, signatureVerified: false, valid: false });
const n2 = this.validateKey(e2);
n2.valid || e2 === this.licenseKey || !(null == (i2 = this.validationResult) ? void 0 : i2.valid) ? (this.licenseKey = e2, this.publish(n2), n2.valid || t(`[Apex] ${n2.message}`)) : s(`[Apex] Ignoring license key: ${(_a = n2.message) != null ? _a : "it is not valid"} A valid license is already active on this page.`);
}
static validateKey(e2) {
const t2 = this.parseKey(e2), s2 = this.validateStructure(e2, t2);
if (!s2.valid || !(null == t2 ? void 0 : t2.signature)) return s2;
const i2 = this.verdicts.get(e2);
return false === i2 ? { data: t2.data, expired: false, message: "Invalid license key. The license signature does not verify.", signatureVerified: true, valid: false } : (void 0 === i2 && this.verifySignature(e2, t2, s2), __spreadProps(__spreadValues({}, s2), { signatureVerified: true === i2 }));
}
static _resetSignatureState() {
this.verdicts.clear(), this.verifying.clear(), this.warnedUnverifiable = false, this.epoch++;
}
static base64ToBytes(e2) {
const t2 = e2.replace(/-/g, "+").replace(/_/g, "/"), s2 = t2.padEnd(4 * Math.ceil(t2.length / 4), "="), i2 = globalThis.atob;
if ("function" != typeof i2) throw new Error("no base64 decoder available");
const n2 = i2(s2), r2 = new Uint8Array(n2.length);
for (let e3 = 0; e3 < n2.length; e3++) r2[e3] = n2.charCodeAt(e3);
return r2;
}
static canonicalPayload(e2) {
const t2 = e2.domains && e2.domains.length > 0 ? e2.domains.join(",") : "";
return `v1|${e2.issueDate}|${e2.expiryDate}|${e2.plan}|${t2}`;
}
static notify(e2) {
for (const t2 of this.listeners) try {
t2(e2);
} catch (e3) {
}
}
static parseKey(e2) {
if ("string" != typeof e2 || !e2.startsWith(i)) return null;
const t2 = e2.slice(5);
if (!t2) return null;
try {
const e3 = new TextDecoder().decode(this.base64ToBytes(t2)), s2 = JSON.parse(e3);
return s2.issueDate && s2.expiryDate && s2.plan ? { data: { domains: Array.isArray(s2.domains) ? s2.domains : void 0, expiryDate: s2.expiryDate, issueDate: s2.issueDate, plan: s2.plan, valid: true }, signature: "string" == typeof s2.sig && s2.sig ? s2.sig : null } : null;
} catch (e3) {
return null;
}
}
static publish(e2) {
this.validationResult = e2, this.notify(e2);
}
static validateStructure(e2, t2) {
const s2 = (e3) => ({ expired: false, message: e3, signatureVerified: false, valid: false });
if ("string" != typeof e2 || !e2.startsWith(i)) return s2('Invalid license key format. License key must start with "APEX-".');
if (!t2) return s2("Invalid license key. Unable to decode license data.");
const { data: r2, signature: a2 } = t2;
if (!a2 && /* @__PURE__ */ new Date() >= n) return s2("This license key is in the old unsigned format, which is no longer accepted. Please request a replacement key.");
if (new Date(r2.expiryDate) < /* @__PURE__ */ new Date()) return { data: r2, expired: true, message: `License expired on ${r2.expiryDate}. Please renew your license.`, signatureVerified: false, valid: false };
if (r2.domains && r2.domains.length > 0) {
const e3 = "undefined" == typeof location ? "" : location.hostname;
if (!r2.domains.some(((t3) => e3 === t3 || e3.endsWith(`.${t3}`)))) return { data: r2, expired: false, message: `License is not valid for this domain (${e3}). Allowed domains: ${r2.domains.join(", ")}.`, signatureVerified: false, valid: false };
}
return { data: r2, expired: false, signatureVerified: false, valid: true };
}
static verifySignature(e2, i2, n2) {
return __async(this, null, function* () {
var r2;
if (this.verifying.has(e2) || this.verdicts.has(e2)) return;
this.verifying.add(e2);
const a2 = this.epoch, l2 = null == (r2 = globalThis.crypto) ? void 0 : r2.subtle;
if (!l2 || 0 === this.publicKeysSpki.length) return this.verifying.delete(e2), void (this.warnedUnverifiable || (this.warnedUnverifiable = true, s(l2 ? "[Apex] No license signing key is configured in this build, so license signatures cannot be verified." : "[Apex] Web Crypto is unavailable (a secure context is required), so the license signature cannot be verified.")));
const o2 = new TextEncoder().encode(this.canonicalPayload(i2.data));
let c2 = false;
for (const e3 of this.publicKeysSpki) {
try {
const t2 = yield l2.importKey("spki", this.base64ToBytes(e3), { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
c2 = yield l2.verify({ hash: "SHA-256", name: "ECDSA" }, t2, this.base64ToBytes(i2.signature), o2);
} catch (e4) {
c2 = false;
}
if (c2) break;
}
if (this.verifying.delete(e2), this.epoch !== a2) return;
if (this.verdicts.set(e2, c2), c2) {
const t2 = __spreadProps(__spreadValues({}, n2), { signatureVerified: true });
return void (this.licenseKey === e2 ? this.publish(t2) : this.notify(t2));
}
const h2 = "Invalid license key. The license signature does not verify.", d = { data: i2.data, expired: false, message: h2, signatureVerified: true, valid: false };
this.licenseKey === e2 ? this.publish(d) : this.notify(d), t(`[Apex] ${h2}`);
});
}
};
l.publicKeysSpki = ["MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQIaK9UMD6n0oR/FIy8QdL0uSzKMQlf1BB+tOrji4/WuHsyRNxeDhVykoSsNURozMi1xhmqWvBH1L//xIfugTPA=="], l.verdicts = /* @__PURE__ */ new Map(), l.verifying = /* @__PURE__ */ new Set(), l.warnedUnverifiable = false, l.epoch = 0;
let o = l;
const c = class {
static applyStyles(e2) {
Object.assign(e2.style, this.CRITICAL_STYLES, { backgroundImage: this.createWatermarkPattern(), backgroundRepeat: "repeat" });
}
static node(e2) {
return e2 ? e2.querySelector(`[${this.WATERMARK_ATTR}]`) : null;
}
static add(e2, t2) {
return e2 && "undefined" != typeof document ? (this.setManaged(e2, t2), this.paint(e2)) : null;
}
static exists(e2) {
return !!this.node(e2);
}
static remove(e2, t2) {
e2 && (this.setManaged(e2, t2), this.erase(e2));
}
static untrack(e2) {
this.managed.delete(e2);
}
static createWatermarkPattern() {
const e2 = this.WATERMARK_TEXT;
return `url("data:image/svg+xml,${encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
<text
x="50%"
y="50%"
dominant-baseline="middle"
text-anchor="middle"
font-family="-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Arial, sans-serif"
font-size="18"
font-weight="600"
fill="rgba(134, 134, 134, 0.1)"
transform="rotate(-35, 100, 60)"
>${e2}</text>
</svg>
`.trim())}")`;
}
static erase(e2) {
var t2;
null == (t2 = this.node(e2)) || t2.remove();
}
static paint(e2) {
let t2 = this.node(e2);
return t2 || (t2 = document.createElement("div"), t2.setAttribute(this.WATERMARK_ATTR, ""), e2.appendChild(t2)), this.applyStyles(t2), "function" == typeof getComputedStyle && "static" === getComputedStyle(e2).position && (e2.style.position = "relative"), t2;
}
static reconcile() {
const e2 = o.isLicenseValid();
for (const t2 of this.managed) t2.isConnected ? e2 ? this.erase(t2) : this.paint(t2) : this.managed.delete(t2);
}
static setManaged(e2, t2) {
false !== (null == t2 ? void 0 : t2.manage) ? this.track(e2) : this.managed.delete(e2);
}
static track(e2) {
this.managed.add(e2), this.subscribed || (this.subscribed = true, o.onChange((() => {
this.reconcile();
})));
}
};
c.WATERMARK_ATTR = "data-apexcharts-watermark", c.WATERMARK_TEXT = "APEXCHARTS", c.ATTR = "data-apexcharts-watermark", c.CRITICAL_STYLES = { bottom: "0", display: "block", left: "0", msUserSelect: "none", opacity: "1", pointerEvents: "none", position: "absolute", right: "0", top: "0", userSelect: "none", visibility: "visible", webkitUserSelect: "none", zIndex: "10000" }, c.managed = /* @__PURE__ */ new Set(), c.subscribed = false;
let h = c;
const PRICING_URL = "https://apexcharts.com/pricing";
let _perspectivesTokenDecoded = false;
const enforced = /* @__PURE__ */ new Set();
function markPerspectivesTokenDecoded() {
_perspectivesTokenDecoded = true;
reevaluateLicenseAcrossCharts();
}
function premiumFeaturesInUse(w, ctx) {
const chart = w && w.config && w.config.chart || {};
const used = [];
if (chart.type === "unit") used.push("unit");
if (ctx.trellis && typeof ctx.trellis.isActive === "function" && ctx.trellis.isActive()) {
used.push("trellis");
}
if (ctx.storyboard && ctx.storyboard._used) used.push("storyboard");
const link = chart.link;
if (ctx.linkedViews && link && (link.enabled === true || typeof link.dimension === "function")) {
used.push("link");
}
if (ctx.ink && chart.ink && chart.ink.enabled === true) used.push("ink");
if (ctx.measure && chart.measure && chart.measure.enabled === true) {
used.push("measure");
}
if (ctx.contextMenu && chart.contextMenu && chart.contextMenu.enabled === true) {
used.push("context-menu");
}
if (ctx.perspectives && (ctx.perspectives._used || _perspectivesTokenDecoded)) {
used.push("perspectives");
}
if (ctx.history && chart.history && chart.history.enabled === true) {
used.push("history");
}
return used;
}
function resolveKey(w) {
const perChart = w && w.config && w.config.chart && w.config.chart.license;
if (perChart) return perChart;
const singleton = o.getKey();
if (singleton) return singleton;
const apex = Environment.getApex();
if (apex && apex.license) return apex.license;
return null;
}
const PREMIUM_PLANS = /* @__PURE__ */ new Set(["premium", "enterprise"]);
function licensedForPremium(key) {
if (!key) return false;
const result = o.validateKey(key);
if (!result.valid) return false;
const plan = result.data && result.data.plan;
return typeof plan === "string" && PREMIUM_PLANS.has(plan.toLowerCase());
}
function reinstateWatermark(ctx, elWrap) {
const node = h.add(elWrap, { manage: false });
if (!node || typeof MutationObserver === "undefined") return;
if (ctx._wmNodeObserver && ctx._wmObservedNode === node) return;
if (ctx._wmNodeObserver) ctx._wmNodeObserver.disconnect();
const nodeObs = new MutationObserver(() => {
const n2 = h.node(elWrap);
if (!n2) return;
nodeObs.disconnect();
h.applyStyles(n2);
nodeObs.takeRecords();
nodeObs.observe(n2, { attributes: true, attributeFilter: ["style"] });
});
nodeObs.observe(node, { attributes: true, attributeFilter: ["style"] });
ctx._wmNodeObserver = nodeObs;
ctx._wmObservedNode = node;
}
function addWatermark(ctx, elWrap) {
reinstateWatermark(ctx, elWrap);
if (typeof MutationObserver === "undefined" || ctx._wmWrapObserver) return;
const wrapObs = new MutationObserver(() => {
if (!h.node(elWrap)) reinstateWatermark(ctx, elWrap);
});
wrapObs.observe(elWrap, { childList: true });
ctx._wmWrapObserver = wrapObs;
}
function teardownWatermark(ctx, elWrap) {
if (ctx._wmWrapObserver) {
ctx._wmWrapObserver.disconnect();
ctx._wmWrapObserver = null;
}
if (ctx._wmNodeObserver) {
ctx._wmNodeObserver.disconnect();
ctx._wmNodeObserver = null;
}
ctx._wmObservedNode = null;
const wrap = elWrap || ctx.w && ctx.w.dom && ctx.w.dom.elWrap;
if (wrap) h.remove(wrap, { manage: false });
}
function notifyTrial(ctx, key, features) {
if (ctx._premiumLicenseNotified) return;
ctx._premiumLicenseNotified = true;
const many = features.length > 1;
if (!key) {
console.warn(
`[ApexCharts] Premium feature${many ? "s" : ""} in use (${features.join(", ")}) without a license. Running in trial mode with a watermark. Get a license: ${PRICING_URL}`
);
return;
}
const result = o.validateKey(key);
if (result.valid) {
const plan = result.data && result.data.plan || "current";
console.warn(
`[ApexCharts] Premium feature${many ? "s" : ""} in use (${features.join(", ")}) require a Premium or Enterprise license; the ${plan} plan does not include ${many ? "them" : "it"}. Running in trial mode with a watermark. Upgrade: ${PRICING_URL}`
);
return;
}
if (key !== o.getKey()) {
console.error(`[Apex] ${result.message}`);
}
}
function enforceLicense(w, ctx) {
try {
if (!Environment.isBrowser()) return;
if (w && w.globals && w.globals.isDestroyed) {
enforced.delete(ctx);
return;
}
const elWrap = w && w.dom && w.dom.elWrap;
if (!elWrap) return;
const features = premiumFeaturesInUse(w, ctx);
if (features.length === 0) {
enforced.delete(ctx);
teardownWatermark(ctx, elWrap);
return;
}
enforced.add(ctx);
const key = resolveKey(w);
if (licensedForPremium(key)) {
teardownWatermark(ctx, elWrap);
return;
}
addWatermark(ctx, elWrap);
notifyTrial(ctx, key, features);
} catch (e2) {
}
}
function reevaluateLicenseAcrossCharts() {
if (!Environment.isBrowser()) return;
const visited = /* @__PURE__ */ new Set();
const apex = Environment.getApex();
const instances = apex && apex._chartInstances;
if (Array.isArray(instances)) {
instances.forEach((entry) => {
const chart = entry && entry.chart;
if (chart && chart.w && !chart.w.globals.isDestroyed) {
visited.add(chart);
enforceLicense(chart.w, chart);
}
});
}
Array.from(enforced).forEach((ctx) => {
const w = ctx && ctx.w;
const elWrap = w && w.dom && w.dom.elWrap;
if (!w || w.globals.isDestroyed || !elWrap || elWrap.isConnected === false) {
enforced.delete(ctx);
return;
}
if (visited.has(ctx)) return;
enforceLicense(w, ctx);
});
}
o.onChange(reevaluateLicenseAcrossCharts);
class Storyboard {
/**
* @param {import('../../types/internal').ChartStateW} w
* @param {import('../../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.w = w;
this.ctx = ctx;
this._beats = [];
this._observer = null;
this._activeIndex = -1;
this._animate = true;
this._warnedNoPerspectives = false;
this._used = false;
}
/**
* Bind beats to scroll position. Rebinding replaces the previous binding.
* @param {{
* beats?: Array<{ el?: Element, selector?: string, key?: string, view?: any, announce?: string, onEnter?: (chart: any, info: StoryboardBeatInfo) => void }>,
* scroller?: Element | string,
* offset?: number,
* animate?: boolean,
* }} [opts]
* @returns {number} the number of beats bound
*/
bind(opts = {}) {
var _a;
this.unbind();
if (!Environment.isBrowser()) return 0;
const doc = this.ctx.el && this.ctx.el.ownerDocument;
if (!doc || typeof IntersectionObserver === "undefined") return 0;
let root = null;
if (opts.scroller) {
root = typeof opts.scroller === "string" ? doc.querySelector(opts.scroller) : opts.scroller;
}
this._beats = this._resolveBeats(doc, root, opts.beats);
if (!this._beats.length) return 0;
this._animate = opts.animate !== false;
const offset = Math.min(Math.max((_a = opts.offset) != null ? _a : 0.5, 0), 1);
const top = +(offset * 100).toFixed(3);
const bottom = +(100 - offset * 100).toFixed(3);
this._observer = new IntersectionObserver(
(entries) => this._onIntersect(entries),
{ root, rootMargin: `-${top}% 0px -${bottom}%`, threshold: 0 }
);
this._beats.forEach((b) => {
var _a2;
return (_a2 = this._observer) == null ? void 0 : _a2.observe(b.el);
});
this._used = true;
enforceLicense(this.w, this.ctx);
return this._beats.length;
}
/**
* Normalize the beats option, or auto-discover [data-apex-beat] elements in
* document order when no explicit list is given.
* @param {Document} doc
* @param {Element | null} root
* @param {Array<any>} [beatsOpt]
* @returns {StoryboardBeat[]}
* @private
*/
_resolveBeats(doc, root, beatsOpt) {
const beats = [];
if (Array.isArray(beatsOpt)) {
beatsOpt.forEach((b, i2) => {
var _a, _b;
if (!b) return;
const el = b.el && typeof b.el === "object" ? b.el : b.selector ? doc.querySelector(b.selector) : null;
if (!el) {
console.warn(
`apexcharts: storyboard beat ${i2} has no resolvable element; skipped.`
);
return;
}
beats.push({
el,
key: (_b = (_a = b.key) != null ? _a : el.getAttribute("data-apex-beat")) != null ? _b : String(i2),
view: b.view,
options: b.options,
announce: b.announce,
onEnter: typeof b.onEnter === "function" ? b.onEnter : void 0
});
});
return beats;
}
const scope = root || doc;
scope.querySelectorAll("[data-apex-beat]").forEach((el, i2) => {
beats.push({
el,
key: el.getAttribute("data-apex-beat") || String(i2),
view: el.getAttribute("data-apex-view") || void 0,
options: void 0,
announce: el.getAttribute("data-apex-announce") || void 0,
onEnter: void 0
});
});
return beats;
}
/**
* @param {IntersectionObserverEntry[]} entries
* @private
*/
_onIntersect(entries) {
entries.forEach((entry) => {
const idx = this._beats.findIndex((b) => b.el === entry.target);
if (idx < 0) return;
if (entry.isIntersecting) {
this._activate(idx);
} else if (idx === this._activeIndex && entry.rootBounds) {
if (entry.boundingClientRect.top >= entry.rootBounds.bottom && idx > 0) {
this._activate(idx - 1);
}
}
});
}
/**
* Activate a beat: apply its view token, run its callback, announce it and
* fire `beatChange`. Idempotent per beat (re-activating the current beat is
* a no-op), so IO chatter never re-applies a view.
* @param {number} idx
* @param {{ animate?: boolean }} [opts]
* @private
*/
_activate(idx, opts = {}) {
var _a, _b;
if (idx === this._activeIndex) return;
const beat = this._beats[idx];
if (!beat) return;
const direction = idx > this._activeIndex ? "down" : "up";
this._activeIndex = idx;
const animate = (opts.animate !== void 0 ? opts.animate : this._animate) && !prefersReducedMotion();
if (beat.view != null || beat.options) {
if (this.ctx.perspectives) {
const v = (_a = beat.view) != null ? _a : {};
const token = typeof v === "string" || v.view ? v : { view: this._normalizeView(v) };
this.ctx.perspectives.apply(token, {
animate,
mergeOptions: beat.options
});
} else if (!this._warnedNoPerspectives) {
this._warnedNoPerspectives = true;
console.warn(
'apexcharts: storyboard beats carry views but the perspectives feature is not bundled. import "apexcharts/features/storyboard" (which includes it) or drive beats via onEnter.'
);
}
}
const info = { index: idx, key: beat.key, el: beat.el, direction };
if (beat.onEnter) beat.onEnter(this.ctx, info);
if (beat.announce) this._announce(beat.announce);
if (typeof this.w.config.chart.events.beatChange === "function") {
this.w.config.chart.events.beatChange(this.ctx, info);
}
(_b = this.ctx.events) == null ? void 0 : _b.fireEvent("beatChange", [this.ctx, info]);
}
/**
* Fill in the parts of a hand-authored (bare) ViewState that would
* otherwise LEAK between beats. updateOptions merges objects, so a beat
* listing only xaxis annotations would keep a previous beat's point
* annotations; padding every annotation kind with an empty array makes
* each beat fully describe its own state, which is what allows scrubbing
* in both directions. Full tokens (from capture()/encode()) already carry
* the complete set and never pass through here.
* @param {any} view
* @returns {any}
* @private
*/
_normalizeView(view) {
const provided = view.annotations && view.annotations.static || {};
return __spreadProps(__spreadValues({}, view), {
annotations: {
static: __spreadValues({
points: [],
xaxis: [],
yaxis: [],
texts: [],
images: []
}, provided),
dynamic: view.annotations && view.annotations.dynamic || []
}
});
}
/**
* Programmatically jump to a beat by index or author key (also usable
* without scrolling, e.g. from next/prev buttons).
* @param {number | string} indexOrKey
* @param {{ animate?: boolean }} [opts]
*/
goTo(indexOrKey, opts = {}) {
const idx = typeof indexOrKey === "number" ? indexOrKey : this._beats.findIndex((b) => b.key === indexOrKey);
if (idx >= 0 && idx < this._beats.length) this._activate(idx, opts);
}
/**
* @returns {{ index: number, key: string | null } | null} the active beat
*/
current() {
if (this._activeIndex < 0) return null;
const beat = this._beats[this._activeIndex];
return { index: this._activeIndex, key: beat ? beat.key : null };
}
/** Disconnect the observer and drop the beat list. */
unbind() {
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
this._beats = [];
this._activeIndex = -1;
this._used = false;
enforceLicense(this.w, this.ctx);
}
/** Full-destroy cleanup (called from Destroy). */
teardown() {
this.unbind();
}
/**
* Push a beat's announcement to the chart's visually-hidden aria-live
* status region so screen-reader users follow the story too. No-op when
* announcements are disabled or the region is absent.
* @param {string} message
* @private
*/
_announce(message) {
const w = this.w;
if (!w.config.chart.accessibility.announcements.enabled) return;
const baseEl = w.dom.baseEl;
if (!baseEl) return;
const region = baseEl.querySelector(".apexcharts-sr-status");
if (!region) return;
region.textContent = "";
setTimeout(() => {
region.textContent = message;
}, 0);
}
}
const Utils = _core.__apex_Utils;
const VIEWSTATE_VERSION = 1;
function axisWindow(min, max) {
const hasMin = min !== void 0 && min !== null;
const hasMax = max !== void 0 && max !== null;
if (!hasMin && !hasMax) return null;
return { min: hasMin ? min : null, max: hasMax ? max : null };
}
function cloneSelection(sel) {
if (!Array.isArray(sel)) return [];
return sel.map((a2) => Array.isArray(a2) ? a2.slice() : a2);
}
function annotationKind(method, ctx) {
if (typeof method !== "function" || !ctx) return null;
if (method === ctx.addXaxisAnnotation) return "xaxis";
if (method === ctx.addYaxisAnnotation) return "yaxis";
if (method === ctx.addPointAnnotation) return "point";
return null;
}
function addMethodName(kind) {
switch (kind) {
case "xaxis":
return "addXaxisAnnotation";
case "yaxis":
return "addYaxisAnnotation";
case "point":
return "addPointAnnotation";
default:
return null;
}
}
function captureAnnotations(w, ctx) {
const staticAnno = w.config.annotations ? Utils.clone(w.config.annotations) : null;
const dynamic = [];
const mem = w.globals.memory && w.globals.memory.methodsToExec || [];
for (const entry of mem) {
if (!entry || entry.label !== "addAnnotation") continue;
const kind = annotationKind(entry.method, ctx);
if (!kind) continue;
dynamic.push({ kind, params: Utils.clone(entry.params) });
}
return { static: staticAnno, dynamic };
}
function captureMeasure(ctx) {
const m = ctx && ctx.measure;
if (!m || typeof m.getPins !== "function") return null;
const pins = m.getPins();
return Array.isArray(pins) && pins.length ? { pins } : null;
}
function captureViewState(w, ctx) {
var _a, _b;
const cfgX = w.config.xaxis || {};
const cfgYArr = Array.isArray(w.config.yaxis) ? w.config.yaxis : w.config.yaxis ? [w.config.yaxis] : [];
const yWindows = cfgYArr.map(
(y) => axisWindow(y && y.min, y && y.max)
);
const anyY = yWindows.some((yw) => yw !== null);
const theme = w.config.theme;
const drilldown = ctx && ctx.drilldown;
return {
v: VIEWSTATE_VERSION,
window: {
xaxis: axisWindow(cfgX.min, cfgX.max),
yaxis: anyY ? yWindows : null
},
zoomed: !!w.interact.zoomed,
collapsed: (w.globals.collapsedSeriesIndices || []).slice(),
ancillaryCollapsed: (w.globals.ancillaryCollapsedSeriesIndices || []).slice(),
selectedDataPoints: cloneSelection(w.interact.selectedDataPoints),
theme: theme ? { mode: (_a = theme.mode) != null ? _a : null, palette: (_b = theme.palette) != null ? _b : null } : null,
locale: w.config.chart && w.config.chart.defaultLocale || null,
annotations: captureAnnotations(w, ctx),
drill: drilldown && drilldown.depth > 0 ? { path: drilldown.path.slice() } : null,
measure: captureMeasure(ctx)
};
}
function applyCollapsedSet(ctx, targetCollapsed, targetAncillary) {
const w = ctx.w;
if (targetCollapsed == null && targetAncillary == null) return;
const names = w.globals.seriesNames || [];
const target = /* @__PURE__ */ new Set([
...targetCollapsed || [],
...targetAncillary || []
]);
const current = /* @__PURE__ */ new Set([
...w.globals.collapsedSeriesIndices || [],
...w.globals.ancillaryCollapsedSeriesIndices || []
]);
for (let realIndex = 0; realIndex < names.length; realIndex++) {
const name = names[realIndex];
if (name == null) continue;
const shouldCollapse = target.has(realIndex);
const isCollapsed = current.has(realIndex);
if (shouldCollapse && !isCollapsed) {
ctx.hideSeries(name);
} else if (!shouldCollapse && isCollapsed) {
ctx.showSeries(name);
}
}
}
function restoreSelection(ctx, selectedDataPoints) {
if (!Array.isArray(selectedDataPoints)) return;
ctx.w.interact.selectedDataPoints = cloneSelection(selectedDataPoints);
}
function applyViewState(ctx, view, { animate = true, mergeOptions } = {}) {
var _a, _b;
if (!ctx || !view) return;
if (typeof view.v === "number" && view.v > VIEWSTATE_VERSION) {
console.warn(
`[apexcharts] ViewState v${view.v} is newer than this build understands (v${VIEWSTATE_VERSION}); applying best-effort.`
);
}
ctx.clearAnnotations();
const options = mergeOptions ? Utils.clone(mergeOptions) : {};
const xw = view.window && view.window.xaxis;
options.xaxis = Object.assign(
{},
options.xaxis,
xw ? { min: (_a = xw.min) != null ? _a : void 0, max: (_b = xw.max) != null ? _b : void 0 } : { min: void 0, max: void 0 }
);
const yw = view.window && view.window.yaxis;
if (Array.isArray(yw)) {
options.yaxis = yw.map(
(y) => {
var _a2, _b2;
return y ? { min: (_a2 = y.min) != null ? _a2 : void 0, max: (_b2 = y.max) != null ? _b2 : void 0 } : {};
}
);
}
if (view.theme) {
const theme = {};
if (view.theme.mode != null) theme.mode = view.theme.mode;
if (view.theme.palette != null) theme.palette = view.theme.palette;
if (Object.keys(theme).length) {
options.theme = Object.assign({}, options.theme, theme);
}
}
if (view.annotations && view.annotations.static) {
options.annotations = Utils.clone(view.annotations.static);
}
ctx.updateOptions(
options,
false,
animate,
false,
false
);
applyViewInteraction(ctx, view);
}
function applyViewInteraction(ctx, view) {
if (!ctx || !view) return;
const w = ctx.w;
w.interact.zoomed = !!view.zoomed;
applyCollapsedSet(ctx, view.collapsed, view.ancillaryCollapsed);
if (view.annotations && Array.isArray(view.annotations.dynamic)) {
view.annotations.dynamic.forEach((a2) => {
const method = addMethodName(a2.kind);
if (method && typeof ctx[method] === "function") {
ctx[method](a2.params, true);
}
});
}
restoreSelection(ctx, view.selectedDataPoints);
if (view.locale && view.locale !== w.config.chart.defaultLocale) {
ctx.setLocale(view.locale);
}
if (ctx.measure && typeof ctx.measure.setPins === "function") {
ctx.measure.setPins(view.measure && view.measure.pins || []);
}
}
const PERSPECTIVE_VERSION = 1;
const HASH_KEY = "apex";
function toBase64(str) {
if (typeof Buffer !== "undefined") {
return Buffer.from(str, "utf-8").toString("base64");
}
const bytes = new TextEncoder().encode(str);
let bin = "";
for (let i2 = 0; i2 < bytes.length; i2++) bin += String.fromCharCode(bytes[i2]);
return btoa(bin);
}
function fromBase64(b64) {
if (typeof Buffer !== "undefined") {
return Buffer.from(b64, "base64").toString("utf-8");
}
const bin = atob(b64);
const bytes = new Uint8Array(bin.length);
for (let i2 = 0; i2 < bin.length; i2++) bytes[i2] = bin.charCodeAt(i2);
return new TextDecoder().decode(bytes);
}
function base64urlEncode(str) {
return toBase64(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function base64urlDecode(b64url) {
let b64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
while (b64.length % 4) b64 += "=";
return fromBase64(b64);
}
function stripFunctions(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (e2) {
return void 0;
}
}
class Perspectives {
/**
* @param {import('../../types/internal').ChartStateW} w
* @param {import('../../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.w = w;
this.ctx = ctx;
this._saved = [];
this._counter = 0;
this._used = false;
}
/**
* Capture the current chart view as a Perspective token.
* @returns {{ v: number, view: object, options?: Record<string, any> }}
*/
capture() {
const view = captureViewState(this.w, this.ctx);
const token = (
/** @type {any} */
{ v: PERSPECTIVE_VERSION, view }
);
const options = this._serializableDelta();
if (options && Object.keys(options).length) token.options = options;
return token;
}
/**
* Build the whitelisted, function-free option override recorded in the token.
* @returns {Record<string, any>}
* @private
*/
_serializableDelta() {
const cfg = this.w.config;
const whitelist = cfg.chart && cfg.chart.perspectives && cfg.chart.perspectives.serializeOptions || ["theme", "xaxis", "yaxis", "title", "subtitle"];
const delta = {};
whitelist.forEach((path) => {
if (cfg[path] !== void 0) {
const stripped = stripFunctions(cfg[path]);
if (stripped !== void 0) delta[path] = stripped;
}
});
return delta;
}
/**
* Encode a token (or the current capture) to a compact base64url string.
* JSON.stringify drops any functions embedded in annotation params / option
* overrides by construction: a shared link carries data; the opening page
* supplies its own functions from config.
* @param {any} [token]
* @returns {string}
*/
encode(token) {
const t2 = token || this.capture();
return base64urlEncode(JSON.stringify(t2));
}
/**
* Decode a base64url token string. Never throws: returns null on any error
* or version mismatch (with a console warning).
* @param {string} str
* @returns {any | null}
*/
decode(str) {
return Perspectives.decode(str);
}
/**
* Encode the current capture into a `#apex=<token>` URL hash fragment on the
* current location. Browser-only; returns '' under SSR.
* @returns {string}
*/
toURL() {
if (!Environment.isBrowser()) return "";
const encoded = this.encode(this.capture());
const url = new URL(window.location.href);
url.hash = `${HASH_KEY}=${encoded}`;
return url.toString();
}
/**
* Restore a perspective. Accepts a token object or an encoded string. When
* the chart is grouped, applies to every synced chart.
*
* The token's option overrides and `opts.mergeOptions` are folded into the
* view restore's ONE updateOptions call (mergeOptions wins over
* token.options; the view's own fields win over both). A single render is
* deliberate: a second immediate updateOptions would kill the first one's
* animation mid-flight, and a chart.type change applied this way morphs
* (morph feature) inside the same re-render instead of being re-rendered
* over. Consumed by Storyboard for per-beat option payloads.
*
* @param {any} tokenOrString
* @param {{ animate?: boolean, mergeOptions?: Record<string, any> }} [opts]
*/
apply(tokenOrString, opts = {}) {
const token = typeof tokenOrString === "string" ? Perspectives.decode(tokenOrString) : tokenOrString;
if (!token || !token.view) return;
this._used = true;
enforceLicense(this.w, this.ctx);
const animate = opts.animate !== void 0 ? opts.animate : true;
const combined = Utils.extend(
token.options ? Utils.clone(token.options) : {},
opts.mergeOptions || {}
);
const mergeOptions = Object.keys(combined).length ? combined : void 0;
const targets = this.w.config.chart.group ? this.ctx.getSyncedCharts() : [this.ctx];
targets.forEach((chart) => {
applyViewState(chart, token.view, { animate, mergeOptions });
});
}
/**
* Save the current view under a name in the in-memory registry.
* @param {string} name
* @returns {string} generated id
*/
save(name) {
const id = `perspective-${++this._counter}`;
this._saved.push({ id, name: name || id, token: this.capture() });
this._used = true;
enforceLicense(this.w, this.ctx);
return id;
}
/**
* List saved perspectives.
* @returns {{ id: string, name: string, token: any }[]}
*/
list() {
return this._saved.map((s2) => ({ id: s2.id, name: s2.name, token: s2.token }));
}
/**
* Delete a saved perspective by id.
* @param {string} id
*/
delete(id) {
const i2 = this._saved.findIndex((s2) => s2.id === id);
if (i2 > -1) this._saved.splice(i2, 1);
}
/** Drop the saved-views registry (called on full destroy). */
teardown() {
this._saved = [];
this._counter = 0;
}
// ── static, pure helpers (available once the feature is imported) ─────────
/**
* @param {string} str base64url token
* @returns {any | null}
*/
static decode(str) {
if (typeof str !== "string" || !str) return null;
try {
const token = JSON.parse(base64urlDecode(str));
if (!token || typeof token !== "object") return null;
if (token.v !== PERSPECTIVE_VERSION) {
console.warn(
`apexcharts: unsupported perspective version ${token.v} (expected ${PERSPECTIVE_VERSION}).`
);
return null;
}
return token;
} catch (e2) {
console.warn("apexcharts: failed to decode perspective token.", e2);
return null;
}
}
/**
* Parse a `#apex=<token>` fragment out of an href (or the current location in
* a browser). Pure and Node-safe when given an explicit href.
* @param {string} [href]
* @returns {any | null}
*/
static fromURL(href) {
try {
const target = href || (Environment.isBrowser() ? window.location.href : "");
if (!target) return null;
const url = new URL(target);
const hash = url.hash.replace(/^#/, "");
if (!hash) return null;
const pair = hash.split("&").map((p) => p.split("=")).find((p) => p[0] === HASH_KEY);
if (!pair || pair[1] == null) return null;
return Perspectives.decode(decodeURIComponent(pair[1]));
} catch (e2) {
return null;
}
}
}
_core__default.registerFeatures({ perspectives: Perspectives });
_core__default.perspectives = {
/** @param {string} str */
decode: (str) => {
markPerspectivesTokenDecoded();
return Perspectives.decode(str);
},
/** @param {string} [href] */
fromURL: (href) => {
markPerspectivesTokenDecoded();
return Perspectives.fromURL(href);
}
};
_core__default.registerFeatures({ storyboard: Storyboard });
export {
default2 as default
};