UNPKG

apexcharts

Version:

A JavaScript Chart Library

1,236 lines (1,235 loc) 41.9 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)); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; /*! * ApexCharts v6.6.1 * (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 PUBLIC_KEYS_SPKI_BASE64 = [ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQIaK9UMD6n0oR/FIy8QdL0uSzKMQlf1BB+tOrji4/WuHsyRNxeDhVykoSsNURozMi1xhmqWvBH1L//xIfugTPA==" ]; const LEGACY_KEYS_ACCEPTED_UNTIL = /* @__PURE__ */ new Date("2027-07-31T00:00:00Z"); const KEY_PREFIX = "APEX-"; const signatureVerdicts = /* @__PURE__ */ new Map(); const verifying = /* @__PURE__ */ new Set(); const listeners = /* @__PURE__ */ new Set(); let warnedUnverifiable = false; function base64Decode(encoded) { if (typeof atob === "function") return atob(encoded); if (typeof Buffer !== "undefined") { return Buffer.from(encoded, "base64").toString("binary"); } throw new Error("no base64 decoder available"); } function base64ToBytes(base64) { const normalised = base64.replace(/-/g, "+").replace(/_/g, "/"); const padded = normalised.padEnd(Math.ceil(normalised.length / 4) * 4, "="); const binary = base64Decode(padded); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); return bytes; } function canonicalPayload(data) { const domains = data.domains && data.domains.length > 0 ? data.domains.join(",") : ""; return `v1|${data.issueDate}|${data.expiryDate}|${data.plan}|${domains}`; } function currentHostname() { return typeof window !== "undefined" && window.location ? window.location.hostname : ""; } function signatureOf(encodedData) { try { const raw = JSON.parse(base64Decode(encodedData)); return typeof raw.sig === "string" && raw.sig ? raw.sig : null; } catch (e) { return null; } } function notify(result) { listeners.forEach((listener) => { try { listener(result); } catch (e) { } }); } function verifySignature(key, data, signature) { return __async(this, null, function* () { if (verifying.has(key) || signatureVerdicts.has(key)) return; verifying.add(key); const subtle = globalThis.crypto ? globalThis.crypto.subtle : void 0; const accepted = LicenseManager.publicKeysSpki; if (!subtle || accepted.length === 0) { verifying.delete(key); if (!warnedUnverifiable) { warnedUnverifiable = true; console.warn( subtle ? "[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." ); } return; } const signed = new TextEncoder().encode(canonicalPayload(data)); let verified = false; for (const spki of accepted) { try { const publicKey = yield subtle.importKey( "spki", base64ToBytes(spki), { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"] ); verified = yield subtle.verify( { hash: "SHA-256", name: "ECDSA" }, publicKey, base64ToBytes(signature), signed ); } catch (e) { verified = false; } if (verified) break; } verifying.delete(key); signatureVerdicts.set(key, verified); if (!verified) { console.error( "[Apex] Invalid license key. The license signature does not verify." ); } notify(LicenseManager.validateKey(key)); }); } class LicenseManager { /** * Decode license data from an encoded string (base64 + JSON). * @param {string} encodedData * @returns {LicenseData | null} */ static decodeLicenseData(encodedData) { try { const data = JSON.parse(base64Decode(encodedData)); if (!data.issueDate || !data.expiryDate || !data.plan) { return null; } return { domains: Array.isArray(data.domains) ? data.domains : void 0, expiryDate: data.expiryDate, issueDate: data.issueDate, plan: data.plan, valid: true }; } catch (e) { return null; } } /** * The key set via setLicense (or null). Lets the enforcer resolve the * chart.license -> setLicense -> Apex.license precedence. * @returns {null | string} */ static getKey() { return this.licenseKey; } /** * Validation result for the singleton key. * @returns {LicenseValidationResult} */ static getLicenseStatus() { if (!this.licenseKey) { return { expired: false, valid: false }; } this.validationResult = this.validateKey(this.licenseKey); return this.validationResult; } /** * Whether a specific key is valid (pure; no singleton mutation). * @param {string | undefined | null} key * @returns {boolean} */ static isKeyValid(key) { if (!key) return false; return this.validateKey(key).valid; } /** @returns {boolean} whether the singleton key is valid */ static isLicenseValid() { if (!this.licenseKey) return false; return this.getLicenseStatus().valid; } /** * Subscribe to signature verdicts arriving. Returns an unsubscribe function. * * Without this a forged key would go unnoticed by any chart that asked once and * painted. `LicenseEnforcer` uses it to re-evaluate every live chart. * * @param {(result: LicenseValidationResult) => void} listener * @returns {() => void} */ static onChange(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; } /** * Set the global (singleton) license key. console.errors when invalid, to * match the rest of the family. * @param {string} key */ static setLicense(key) { this.licenseKey = key; this.validationResult = this.validateKey(key); if (!this.validationResult.valid) { console.error(`[Apex] ${this.validationResult.message}`); } } /** * Validate an arbitrary key WITHOUT mutating the singleton. Used to resolve * per-chart (`chart.license`) and global (`window.Apex.license`) keys, which * bypass setLicense. This is a superset of the family (which keeps * validateLicense private); the format and rules are identical. * * Synchronous by contract, because it runs during render. Signature checking is * started here and settles later; see `onChange`. * * @param {string} key * @returns {LicenseValidationResult} */ static validateKey(key) { try { if (typeof key !== "string" || !key.startsWith(KEY_PREFIX)) { return { expired: false, message: 'Invalid license key format. License key must start with "APEX-".', signatureVerified: false, valid: false }; } const encodedData = key.slice(KEY_PREFIX.length); if (!encodedData) { return { expired: false, message: "Invalid license key format. Expected format: APEX-{encoded-data}.", signatureVerified: false, valid: false }; } const licenseData = this.decodeLicenseData(encodedData); if (!licenseData) { return { expired: false, message: "Invalid license key. Unable to decode license data.", signatureVerified: false, valid: false }; } const signature = signatureOf(encodedData); if (!signature && /* @__PURE__ */ new Date() >= LEGACY_KEYS_ACCEPTED_UNTIL) { return { data: licenseData, expired: false, message: "This license key is in the old unsigned format, which is no longer accepted. Please request a replacement key.", signatureVerified: false, valid: false }; } const now = /* @__PURE__ */ new Date(); const expiryDate = new Date(licenseData.expiryDate); if (expiryDate < now) { return { data: licenseData, expired: true, message: `License expired on ${licenseData.expiryDate}. Please renew your license.`, signatureVerified: false, valid: false }; } if (licenseData.domains && licenseData.domains.length > 0) { const hostname = currentHostname(); const allowed = licenseData.domains.some( (domain) => hostname === domain || hostname.endsWith(`.${domain}`) ); if (!allowed) { return { data: licenseData, expired: false, message: `License is not valid for this domain (${hostname}). Allowed domains: ${licenseData.domains.join(", ")}.`, signatureVerified: false, valid: false }; } } if (signature) { const verdict = signatureVerdicts.get(key); if (verdict === false) { return { data: licenseData, expired: false, message: "Invalid license key. The license signature does not verify.", signatureVerified: true, valid: false }; } if (verdict === void 0) { void verifySignature(key, licenseData, signature); } return { data: licenseData, expired: false, signatureVerified: verdict === true, valid: true }; } return { data: licenseData, expired: false, signatureVerified: false, valid: true }; } catch (e) { return { expired: false, message: "Invalid license key format or corrupted data.", signatureVerified: false, valid: false }; } } /** Test-only: forget signature verdicts and the one-time warnings. */ static _resetSignatureState() { signatureVerdicts.clear(); verifying.clear(); warnedUnverifiable = false; } } /** @type {null | string} */ __publicField(LicenseManager, "licenseKey", null); /** * Accepted signing keys. Replaced by tests with an ephemeral keypair, since * they cannot sign for the production key. Not public API. * @type {string[]} */ __publicField(LicenseManager, "publicKeysSpki", PUBLIC_KEYS_SPKI_BASE64); /** @type {LicenseValidationResult | null} */ __publicField(LicenseManager, "validationResult", null); const WATERMARK_ATTR = "data-apexcharts-watermark"; const WATERMARK_TEXT = "APEXCHARTS"; const CRITICAL_STYLES = { position: "absolute", top: "0", right: "0", bottom: "0", left: "0", pointerEvents: "none", userSelect: "none", webkitUserSelect: "none", msUserSelect: "none", zIndex: "10000", display: "block", visibility: "visible", opacity: "1" }; function createWatermarkPattern() { const svg = ` <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)" >${WATERMARK_TEXT}</text> </svg> `; return `url("data:image/svg+xml,${encodeURIComponent(svg.trim())}")`; } class Watermark { /** * Apply the overlay's critical styles + background to a node. Split out so a * MutationObserver can restore styles after tampering. * @param {HTMLElement} el */ static applyStyles(el) { Object.assign(el.style, CRITICAL_STYLES, { backgroundImage: createWatermarkPattern(), backgroundRepeat: "repeat" }); } /** * Add the watermark to a container, reusing the existing node if present (so * a style-tamper observer bound to it stays valid across re-renders). No-op * when there is no document (SSR) or no container. * @param {HTMLElement | null | undefined} container * @returns {HTMLElement | null} the watermark node */ static add(container) { if (!container || typeof document === "undefined") return null; let watermark = this.node(container); if (!watermark) { watermark = document.createElement("div"); watermark.setAttribute(WATERMARK_ATTR, ""); container.appendChild(watermark); } this.applyStyles(watermark); if (typeof getComputedStyle === "function" && getComputedStyle(container).position === "static") { container.style.position = "relative"; } return watermark; } /** * @param {HTMLElement | null | undefined} container * @returns {HTMLElement | null} the watermark node, if present */ static node(container) { if (!container) return null; return ( /** @type {HTMLElement | null} */ container.querySelector(`[${WATERMARK_ATTR}]`) ); } /** * @param {HTMLElement | null | undefined} container * @returns {boolean} */ static exists(container) { return !!this.node(container); } /** * Remove the watermark from a container. * @param {HTMLElement | null | undefined} container */ static remove(container) { const existing = this.node(container); if (existing) existing.remove(); } } __publicField(Watermark, "ATTR", WATERMARK_ATTR); 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.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 = LicenseManager.getKey(); if (singleton) return singleton; const apex = Environment.getApex(); if (apex && apex.license) return apex.license; return null; } function reinstateWatermark(ctx, elWrap) { const node = Watermark.add(elWrap); if (!node || typeof MutationObserver === "undefined") return; if (ctx._wmNodeObserver && ctx._wmObservedNode === node) return; if (ctx._wmNodeObserver) ctx._wmNodeObserver.disconnect(); const nodeObs = new MutationObserver(() => { const n = Watermark.node(elWrap); if (!n) return; nodeObs.disconnect(); Watermark.applyStyles(n); nodeObs.takeRecords(); nodeObs.observe(n, { 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 (!Watermark.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) Watermark.remove(wrap); } function notifyTrial(ctx, key, features) { if (ctx._premiumLicenseNotified) return; ctx._premiumLicenseNotified = true; if (!key) { console.warn( `[ApexCharts] Premium feature${features.length > 1 ? "s" : ""} in use (${features.join(", ")}) without a license. Running in trial mode with a watermark. Get a license: ${PRICING_URL}` ); return; } if (key !== LicenseManager.getKey()) { console.error(`[Apex] ${LicenseManager.validateKey(key).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 (LicenseManager.isKeyValid(key)) { teardownWatermark(ctx, elWrap); return; } addWatermark(ctx, elWrap); notifyTrial(ctx, key, features); } catch (e) { } } 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); }); } LicenseManager.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, i) => { 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 ${i} has no resolvable element; skipped.` ); return; } beats.push({ el, key: (_b = (_a = b.key) != null ? _a : el.getAttribute("data-apex-beat")) != null ? _b : String(i), 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, i) => { beats.push({ el, key: el.getAttribute("data-apex-beat") || String(i), 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((a) => Array.isArray(a) ? a.slice() : a); } 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((a) => { const method = addMethodName(a.kind); if (method && typeof ctx[method] === "function") { ctx[method](a.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 i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); 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 i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); 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 (e) { 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 t = token || this.capture(); return base64urlEncode(JSON.stringify(t)); } /** * 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((s) => ({ id: s.id, name: s.name, token: s.token })); } /** * Delete a saved perspective by id. * @param {string} id */ delete(id) { const i = this._saved.findIndex((s) => s.id === id); if (i > -1) this._saved.splice(i, 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 (e) { console.warn("apexcharts: failed to decode perspective token.", e); 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 (e) { 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 };