deepspotscreen-sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +485 -0
  2. package/dist/DeepspotSDK-CyX06_c2.js +1486 -0
  3. package/dist/DeepspotSDK-g7o9tAip.cjs +546 -0
  4. package/dist/_commonjsHelpers-MeVG4QFm.cjs +1 -0
  5. package/dist/_commonjsHelpers-bAxELxBV.js +8 -0
  6. package/dist/apexcharts.common-DGzslxI3.cjs +808 -0
  7. package/dist/apexcharts.common-k3hLWpB8.js +8956 -0
  8. package/dist/components/DashboardElement.d.ts +18 -0
  9. package/dist/components/ReportElement.d.ts +15 -0
  10. package/dist/core/ApiClient.d.ts +41 -0
  11. package/dist/core/DeepspotSDK.d.ts +11 -0
  12. package/dist/core/EmbedInstance.d.ts +46 -0
  13. package/dist/deepspot-sdk.esm.js +1704 -0
  14. package/dist/deepspot-sdk.js +1655 -0
  15. package/dist/html2canvas.esm-CzwMv54K.js +4870 -0
  16. package/dist/html2canvas.esm-EoNFmhrp.cjs +22 -0
  17. package/dist/index.d.ts +146 -0
  18. package/dist/index.es-C-ai08UC.js +5632 -0
  19. package/dist/index.es-ELJ1Oc0z.cjs +18 -0
  20. package/dist/index.es-TiU5c3LF.js +5632 -0
  21. package/dist/jspdf.es.min-D6GjoB2T.cjs +243 -0
  22. package/dist/jspdf.es.min-Ge0fRUwj.js +8107 -0
  23. package/dist/jspdf.es.min-d9hlG26J.js +8107 -0
  24. package/dist/layout/GridLayout.d.ts +25 -0
  25. package/dist/purify.es-CuxL4pit.js +471 -0
  26. package/dist/purify.es-D_pYQKft.cjs +3 -0
  27. package/dist/react/index.cjs +1 -0
  28. package/dist/react/index.d.ts +33 -0
  29. package/dist/react/index.mjs +188 -0
  30. package/dist/renderer/CardRenderer.d.ts +6 -0
  31. package/dist/renderer/ChartRenderer.d.ts +19 -0
  32. package/dist/renderer/DashboardRenderer.d.ts +64 -0
  33. package/dist/renderer/FilterRenderer.d.ts +20 -0
  34. package/dist/renderer/ReportRenderer.d.ts +14 -0
  35. package/dist/renderer/TableRenderer.d.ts +8 -0
  36. package/dist/types.d.ts +140 -0
  37. package/dist/utils.d.ts +4 -0
  38. package/dist/vue/index.cjs +1 -0
  39. package/dist/vue/index.d.ts +212 -0
  40. package/dist/vue/index.mjs +138 -0
  41. package/package.json +69 -0
@@ -0,0 +1,1486 @@
1
+ class A {
2
+ constructor(e, t) {
3
+ this.tokenCache = /* @__PURE__ */ new Map(), this.baseUrl = e.replace(/\/$/, ""), this.apiKey = t;
4
+ }
5
+ // ── Embed Token ─────────────────────────────────────────────────────────────
6
+ async getEmbedToken(e) {
7
+ var i, a, d, o;
8
+ const t = [
9
+ e.dashboardId,
10
+ e.embedType,
11
+ (i = e.embedLevel) != null ? i : "",
12
+ (a = e.componentId) != null ? a : "",
13
+ (d = e.userId) != null ? d : ""
14
+ ].join(":"), r = this.tokenCache.get(t);
15
+ if (r && r.expiresAt > Date.now() + 6e4) return r.token;
16
+ const s = await this.post(
17
+ "/dashboard-builder/embed-token",
18
+ {
19
+ dashboardId: e.dashboardId,
20
+ embedType: e.embedType,
21
+ embedLevel: (o = e.embedLevel) != null ? o : "dashboard",
22
+ componentId: e.componentId,
23
+ userId: e.userId,
24
+ tenantId: e.tenantId,
25
+ expiresIn: 3600
26
+ },
27
+ { "x-deepspot-api-key": this.apiKey }
28
+ );
29
+ return this.tokenCache.set(t, {
30
+ token: s.token,
31
+ expiresAt: new Date(s.expiresAt).getTime()
32
+ }), s.token;
33
+ }
34
+ // ── Dashboard Render (lazy — loads one tab at a time) ──────────────────────
35
+ async getDashboardRender(e, t, r = {}) {
36
+ var i;
37
+ const s = new URLSearchParams({
38
+ embedType: "dashboard",
39
+ embedLevel: (i = r.embedLevel) != null ? i : "dashboard"
40
+ });
41
+ return r.pageId && s.set("pageId", r.pageId), r.tabId && s.set("tabId", r.tabId), r.filters && Object.entries(r.filters).forEach(([a, d]) => {
42
+ d != null && d !== "" && s.set(`filter[${a}]`, Array.isArray(d) ? d.join(",") : String(d));
43
+ }), this.get(
44
+ `/dashboard-builder/embed/${e}/render?${s}`,
45
+ { "x-embed-token": t }
46
+ );
47
+ }
48
+ // ── Single Report Render ────────────────────────────────────────────────────
49
+ async getReportRender(e, t, r, s = {}) {
50
+ const i = new URLSearchParams({
51
+ embedType: "report",
52
+ embedLevel: "report",
53
+ componentId: t
54
+ });
55
+ return s.filters && Object.entries(s.filters).forEach(([a, d]) => {
56
+ d != null && d !== "" && i.set(`filter[${a}]`, Array.isArray(d) ? d.join(",") : String(d));
57
+ }), this.get(
58
+ `/dashboard-builder/embed/${e}/render?${i}`,
59
+ { "x-embed-token": r }
60
+ );
61
+ }
62
+ // ── API Key Management ──────────────────────────────────────────────────────
63
+ async createApiKey(e, t) {
64
+ return this.post("/dashboard-builder/api-keys", e, {
65
+ Authorization: `Bearer ${t}`
66
+ });
67
+ }
68
+ async listApiKeys(e) {
69
+ return this.get("/dashboard-builder/api-keys", {
70
+ Authorization: `Bearer ${e}`
71
+ });
72
+ }
73
+ async deleteApiKey(e, t) {
74
+ await this.delete(`/dashboard-builder/api-keys/${e}`, {
75
+ Authorization: `Bearer ${t}`
76
+ });
77
+ }
78
+ // ── Filter Options ──────────────────────────────────────────────────────────
79
+ /**
80
+ * Fetches distinct option values for a dropdown / multi-select filter.
81
+ * Mirrors the main dashboard call:
82
+ * GET /dashboard-builder/{dashboardId}/filters/{filterId}/options
83
+ * The embed token is sent so the backend can validate the caller has
84
+ * access to this dashboard without requiring an admin JWT.
85
+ */
86
+ async getFilterOptions(e, t, r) {
87
+ try {
88
+ const s = await this.get(
89
+ `/dashboard-builder/embed/${e}/filters/${t}/options`,
90
+ { "x-embed-token": r }
91
+ );
92
+ return Array.isArray(s) ? s : Array.isArray(s == null ? void 0 : s.options) ? s.options : Array.isArray(s == null ? void 0 : s.data) ? s.data : [];
93
+ } catch (s) {
94
+ return [];
95
+ }
96
+ }
97
+ clearTokenCache() {
98
+ this.tokenCache.clear();
99
+ }
100
+ // ── Private helpers ─────────────────────────────────────────────────────────
101
+ async get(e, t = {}) {
102
+ const r = await fetch(`${this.baseUrl}${e}`, {
103
+ method: "GET",
104
+ headers: { "Content-Type": "application/json", ...t }
105
+ });
106
+ return this.handleResponse(r);
107
+ }
108
+ async post(e, t, r = {}) {
109
+ const s = await fetch(`${this.baseUrl}${e}`, {
110
+ method: "POST",
111
+ headers: { "Content-Type": "application/json", ...r },
112
+ body: JSON.stringify(t)
113
+ });
114
+ return this.handleResponse(s);
115
+ }
116
+ async delete(e, t = {}) {
117
+ const r = await fetch(`${this.baseUrl}${e}`, {
118
+ method: "DELETE",
119
+ headers: { "Content-Type": "application/json", ...t }
120
+ });
121
+ if (!r.ok) throw new Error(`Deepspot SDK: DELETE ${e} failed (${r.status})`);
122
+ }
123
+ async handleResponse(e) {
124
+ if (!e.ok) {
125
+ let r = `HTTP ${e.status}`;
126
+ try {
127
+ const s = await e.json();
128
+ r = (s == null ? void 0 : s.message) || (s == null ? void 0 : s.error) || r;
129
+ } catch (s) {
130
+ }
131
+ throw new Error(`Deepspot SDK: ${r}`);
132
+ }
133
+ const t = await e.json();
134
+ return (t == null ? void 0 : t.data) !== void 0 ? t.data : t;
135
+ }
136
+ }
137
+ class $ {
138
+ constructor(e) {
139
+ var t, r;
140
+ this.destroyed = !1, this.refreshTimer = null, this.opts = e, this.filters = { ...e.activeFilters }, this.activePageId = (t = e.activePageId) != null ? t : "", this.activeTabId = (r = e.activeTabId) != null ? r : "";
141
+ }
142
+ /** Returns a copy of the currently applied filters */
143
+ getActiveFilters() {
144
+ return { ...this.filters };
145
+ }
146
+ /** Apply a single filter and re-fetch data */
147
+ setFilter(e, t) {
148
+ this.filters[e] = t, this.scheduleRefresh();
149
+ }
150
+ /** Apply multiple filters at once and re-fetch */
151
+ setFilters(e) {
152
+ Object.assign(this.filters, e), this.scheduleRefresh();
153
+ }
154
+ /** Navigate to a different page (dashboard embed only) */
155
+ goToPage(e) {
156
+ this.opts.embedType === "dashboard" && (this.activePageId = e, this.opts.renderer.goToPage(e));
157
+ }
158
+ /** Navigate to a specific tab on a specific page */
159
+ goToTab(e, t) {
160
+ this.opts.embedType === "dashboard" && (this.activePageId = e, this.activeTabId = t, this.opts.renderer.goToTab(e, t));
161
+ }
162
+ /** Force re-fetch all data from backend */
163
+ async refresh() {
164
+ if (!this.destroyed)
165
+ try {
166
+ const e = await this.fetchData();
167
+ this.opts.renderer.update(e);
168
+ } catch (e) {
169
+ console.error("Deepspot SDK: refresh failed", e);
170
+ }
171
+ }
172
+ /** Export PDF (dashboard embed only) */
173
+ exportPDF() {
174
+ this.opts.embedType === "dashboard" && this.opts.renderer.exportPDF();
175
+ }
176
+ /** Remove the embed and clean up */
177
+ destroy() {
178
+ this.destroyed = !0, this.refreshTimer && clearTimeout(this.refreshTimer), this.opts.renderer.destroy(), this.opts.apiClient.clearTokenCache();
179
+ }
180
+ // ── Private ─────────────────────────────────────────────────────────────────
181
+ /** Debounce rapid filter changes into a single fetch */
182
+ scheduleRefresh() {
183
+ this.refreshTimer && clearTimeout(this.refreshTimer), this.refreshTimer = setTimeout(() => this.refresh(), 300);
184
+ }
185
+ async fetchData() {
186
+ const { apiClient: e, dashboardId: t, componentId: r, token: s, embedType: i, embedLevel: a } = this.opts;
187
+ return i === "report" && r ? e.getReportRender(t, r, s, {
188
+ filters: this.filters
189
+ }) : e.getDashboardRender(t, s, {
190
+ embedLevel: a,
191
+ pageId: this.activePageId || void 0,
192
+ tabId: this.activeTabId || void 0,
193
+ filters: this.filters
194
+ });
195
+ }
196
+ }
197
+ const D = 24, x = 10, w = 12;
198
+ class I {
199
+ /**
200
+ * @param containerWidth Actual pixel width of the grid container.
201
+ * @param minY Minimum y value across all components (normalises
202
+ * the grid so the topmost component starts at top=6px).
203
+ */
204
+ constructor(e, t = 0) {
205
+ this.containerWidth = e, this.minY = t;
206
+ }
207
+ /** Pixel width of one column unit */
208
+ get colWidth() {
209
+ return this.containerWidth / D;
210
+ }
211
+ /** Convert grid position {x,y,w,h} → CSS absolute pixel values */
212
+ toPx(e) {
213
+ const t = this.colWidth, r = w / 2;
214
+ return {
215
+ left: e.x * t + r,
216
+ top: (e.y - this.minY) * x + r,
217
+ width: e.w * t - w,
218
+ height: e.h * x - w
219
+ };
220
+ }
221
+ /** Total pixel height required to fit all positioned components */
222
+ static totalHeight(e) {
223
+ if (!e.length) return 400;
224
+ const t = Math.min(...e.map((s) => s.y));
225
+ return (Math.max(...e.map((s) => s.y + s.h)) - t) * x + 40;
226
+ }
227
+ /** Apply absolute positioning styles directly to a DOM element */
228
+ applyStyles(e, t) {
229
+ const r = this.toPx(t);
230
+ e.style.position = "absolute", e.style.left = `${r.left}px`, e.style.top = `${r.top}px`, e.style.width = `${r.width}px`, e.style.height = `${r.height}px`;
231
+ }
232
+ }
233
+ let T = null;
234
+ function E() {
235
+ return T || (T = import("./apexcharts.common-k3hLWpB8.js").then((p) => p.a)), T;
236
+ }
237
+ class L {
238
+ constructor(e) {
239
+ this.chart = null, this.container = e;
240
+ }
241
+ async render(e, t, r, s) {
242
+ if (!t || t.length === 0) {
243
+ this.renderEmpty(e.title);
244
+ return;
245
+ }
246
+ const i = this.buildOptions(e, t, r, s);
247
+ if (!i) {
248
+ this.renderEmpty(e.title);
249
+ return;
250
+ }
251
+ this.container.innerHTML = `
252
+ <div class="ds-chart-card">
253
+ <div class="ds-chart-title">${e.title || ""}</div>
254
+ <div class="ds-chart-body" id="ds-chart-body-${e.id}"></div>
255
+ </div>
256
+ `;
257
+ const a = this.container.querySelector(`#ds-chart-body-${e.id}`);
258
+ if (a)
259
+ try {
260
+ this.chart && this.chart.destroy();
261
+ const { default: d } = await E();
262
+ this.chart = new d(a, i), this.chart.render();
263
+ } catch (d) {
264
+ console.error("[Deepspot SDK] ChartRenderer error:", d), a.innerHTML = '<div class="ds-chart-empty">Chart render error</div>';
265
+ }
266
+ }
267
+ async update(e, t, r) {
268
+ if (!this.chart || !t || t.length === 0) {
269
+ await this.render(e, t, r);
270
+ return;
271
+ }
272
+ const { series: s, categories: i } = this.extractSeriesAndCategories(e, t);
273
+ try {
274
+ this.chart.updateOptions({
275
+ series: s,
276
+ xaxis: { categories: i },
277
+ theme: { mode: r }
278
+ });
279
+ } catch (a) {
280
+ this.render(e, t, r);
281
+ }
282
+ }
283
+ destroy() {
284
+ var e;
285
+ try {
286
+ (e = this.chart) == null || e.destroy();
287
+ } catch (t) {
288
+ }
289
+ this.chart = null;
290
+ }
291
+ // ── Private ─────────────────────────────────────────────────────────────────
292
+ buildOptions(e, t, r, s) {
293
+ const i = e.type;
294
+ return i === "pie" || i === "donut" ? this.buildPieOptions(e, t, r, s) : i === "bar" || i === "line" || i === "area" ? this.buildCartesianOptions(e, t, r, s) : null;
295
+ }
296
+ buildCartesianOptions(e, t, r, s) {
297
+ var n;
298
+ const { series: i, categories: a, xAxisLabel: d, yAxisLabel: o } = this.extractSeriesAndCategories(e, t), l = e.properties || {}, b = l.colors ? Object.values(l.colors) : ["#6366f1", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#3b82f6"], c = r === "dark";
299
+ return {
300
+ chart: {
301
+ type: e.type === "area" ? "area" : e.type === "line" ? "line" : "bar",
302
+ toolbar: { show: !1 },
303
+ background: "transparent",
304
+ animations: { enabled: !0, speed: 400 },
305
+ fontFamily: "inherit",
306
+ // Explicit height prevents ApexCharts from guessing the flex-child height
307
+ // at render time (which can read as 0 before the browser settles layout).
308
+ height: s != null ? s : "100%"
309
+ },
310
+ theme: { mode: r },
311
+ series: i,
312
+ xaxis: {
313
+ categories: a,
314
+ title: { text: d },
315
+ labels: {
316
+ rotate: a.length > 8 ? -45 : 0,
317
+ style: { colors: c ? "#94a3b8" : "#6b7280", fontSize: "11px" }
318
+ },
319
+ axisBorder: { color: c ? "#334155" : "#e5e7eb" },
320
+ axisTicks: { color: c ? "#334155" : "#e5e7eb" }
321
+ },
322
+ yaxis: {
323
+ title: { text: o },
324
+ labels: { style: { colors: c ? "#94a3b8" : "#6b7280", fontSize: "11px" } }
325
+ },
326
+ colors: b,
327
+ legend: {
328
+ show: i.length > 1 || ((n = l.showLegend) != null ? n : !1),
329
+ position: "bottom",
330
+ labels: { colors: c ? "#94a3b8" : "#6b7280" }
331
+ },
332
+ grid: {
333
+ borderColor: c ? "#1e293b" : "#f3f4f6",
334
+ strokeDashArray: 4,
335
+ padding: {
336
+ bottom: a.length > 8 ? 60 : 10
337
+ }
338
+ },
339
+ tooltip: { theme: r },
340
+ stroke: {
341
+ curve: "smooth",
342
+ width: e.type === "bar" ? 0 : 2
343
+ },
344
+ fill: e.type === "area" ? { type: "gradient", gradient: { shadeIntensity: 0.5, opacityFrom: 0.4, opacityTo: 0 } } : {},
345
+ dataLabels: { enabled: !1 },
346
+ plotOptions: {
347
+ bar: {
348
+ borderRadius: 4,
349
+ distributed: i.length === 1
350
+ }
351
+ }
352
+ };
353
+ }
354
+ buildPieOptions(e, t, r, s) {
355
+ const i = e.properties || {}, a = t.length > 0 ? Object.keys(t[0]) : [], d = a[0] || "label", o = a[1] || "value", l = t.map((h) => {
356
+ var f;
357
+ return String((f = h[d]) != null ? f : "");
358
+ }), b = t.map((h) => Number(h[o]) || 0), c = r === "dark", n = i.colors ? Object.values(i.colors) : ["#6366f1", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#3b82f6"];
359
+ return {
360
+ chart: {
361
+ type: e.type,
362
+ toolbar: { show: !1 },
363
+ background: "transparent",
364
+ fontFamily: "inherit",
365
+ height: s != null ? s : "100%"
366
+ },
367
+ theme: { mode: r },
368
+ series: b,
369
+ labels: l,
370
+ colors: n,
371
+ legend: {
372
+ position: "bottom",
373
+ labels: { colors: c ? "#94a3b8" : "#6b7280" }
374
+ },
375
+ tooltip: { theme: r },
376
+ dataLabels: { enabled: l.length <= 8 },
377
+ plotOptions: {
378
+ pie: {
379
+ donut: { size: e.type === "donut" ? "65%" : "0%" }
380
+ }
381
+ }
382
+ };
383
+ }
384
+ extractSeriesAndCategories(e, t) {
385
+ const r = e.properties || {}, s = t.length > 0 ? Object.keys(t[0]) : [], i = r.xAxis || s[0] || "x";
386
+ let a = [];
387
+ if (r.selectedYAxisColumn)
388
+ try {
389
+ const n = typeof r.selectedYAxisColumn == "string" ? JSON.parse(r.selectedYAxisColumn) : r.selectedYAxisColumn;
390
+ Array.isArray(n) && (a = n.map(
391
+ (h) => typeof h == "string" ? { column: h } : h
392
+ ));
393
+ } catch (n) {
394
+ }
395
+ if (!a.length && r.yAxis) {
396
+ const n = r.yAxis;
397
+ a = (Array.isArray(n) ? n : [n]).map((f) => ({ column: String(f) }));
398
+ }
399
+ !a.length && s.length > 1 && (a = s.slice(1).map((n) => ({ column: n })));
400
+ const d = t.map((n) => {
401
+ var h;
402
+ return String((h = n[i]) != null ? h : "");
403
+ }), o = t.length > 0 ? Object.keys(t[0]) : [], l = a.map((n) => {
404
+ const h = o.includes(n.column) ? n.column : null, f = h ? null : o.find((u) => u.includes(n.column) || n.column.includes(u)), g = !h && !f ? o.find((u) => {
405
+ var y;
406
+ return u !== i && !isNaN(Number((y = t[0]) == null ? void 0 : y[u]));
407
+ }) : null, m = h || f || g || n.column;
408
+ return {
409
+ name: n.label || n.column.replace(/_/g, " "),
410
+ data: t.map((u) => {
411
+ const y = u[m];
412
+ return y != null ? Number(y) : null;
413
+ })
414
+ };
415
+ }), b = r.xAxisLabel || i.replace(/_/g, " "), c = r.yAxisLabel || (l.length === 1 ? l[0].name : "Values");
416
+ return { series: l, categories: d, xAxisLabel: b, yAxisLabel: c };
417
+ }
418
+ renderEmpty(e) {
419
+ this.container.innerHTML = `
420
+ <div class="ds-chart-card">
421
+ ${e ? `<div class="ds-chart-title">${e}</div>` : ""}
422
+ <div class="ds-chart-empty">No data available</div>
423
+ </div>
424
+ `;
425
+ }
426
+ }
427
+ class R {
428
+ render(e, t, r) {
429
+ var l;
430
+ if (!r || r.length === 0) {
431
+ e.innerHTML = `
432
+ <div class="ds-table-card">
433
+ <div class="ds-table-title">${t.title || ""}</div>
434
+ <div class="ds-chart-empty">No data available</div>
435
+ </div>
436
+ `;
437
+ return;
438
+ }
439
+ const s = Object.keys(r[0]), i = ((l = t.properties) == null ? void 0 : l.columns) || [], a = i.length ? i.filter((b) => s.includes(b)) : s, d = a.map((b) => `<th>${this.formatHeader(b)}</th>`).join(""), o = r.map((b) => `<tr>${a.map((n) => {
440
+ const h = b[n];
441
+ return `<td title="${this.escape(String(h != null ? h : ""))}">${this.escape(this.formatValue(h))}</td>`;
442
+ }).join("")}</tr>`).join("");
443
+ e.innerHTML = `
444
+ <div class="ds-table-card">
445
+ <div class="ds-table-title">${t.title || ""}</div>
446
+ <div class="ds-table-scroll">
447
+ <table class="ds-table">
448
+ <thead><tr>${d}</tr></thead>
449
+ <tbody>${o}</tbody>
450
+ </table>
451
+ </div>
452
+ </div>
453
+ `;
454
+ }
455
+ formatHeader(e) {
456
+ return e.replace(/_/g, " ").replace(/\b\w/g, (t) => t.toUpperCase());
457
+ }
458
+ formatValue(e) {
459
+ return e == null ? "—" : typeof e == "number" ? e % 1 === 0 ? e.toLocaleString() : e.toFixed(2) : String(e);
460
+ }
461
+ escape(e) {
462
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
463
+ }
464
+ }
465
+ class F {
466
+ render(e, t, r) {
467
+ const s = t.properties || {};
468
+ let i = null;
469
+ if (r && r.length > 0) {
470
+ const h = r[0], f = s.metric || Object.keys(h)[0], g = h[f];
471
+ if (g !== void 0)
472
+ i = g;
473
+ else {
474
+ const m = Object.keys(h).find(
475
+ (u) => u.includes(f) || f.includes(u)
476
+ );
477
+ i = h[m != null ? m : Object.keys(h)[0]];
478
+ }
479
+ }
480
+ const a = this.formatValue(i, s), d = s.backgroundColor || "", o = s.textColor || "", l = t.title || "", b = d ? `background:${d};border-color:${d};` : "", c = o ? `color:${o};` : "", n = o ? `color:${o};opacity:0.7;` : "";
481
+ e.innerHTML = `
482
+ <div class="ds-card"${b ? ` style="${b}"` : ""}>
483
+ <div class="ds-card-label"${n ? ` style="${n}"` : ""}>${l}</div>
484
+ <div class="ds-card-value"${c ? ` style="${c}"` : ""}>${a}</div>
485
+ </div>
486
+ `;
487
+ }
488
+ formatValue(e, t) {
489
+ if (e == null) return "—";
490
+ const r = Number(e);
491
+ if (isNaN(r)) return String(e);
492
+ const s = (t == null ? void 0 : t.prefix) || (t == null ? void 0 : t.currencySymbol) || "", i = (t == null ? void 0 : t.suffix) || "";
493
+ let a;
494
+ return Math.abs(r) >= 1e6 ? a = (r / 1e6).toFixed(1) + "M" : Math.abs(r) >= 1e3 ? a = (r / 1e3).toFixed(1) + "K" : a = r % 1 === 0 ? r.toLocaleString() : r.toFixed(2), `${s}${a}${i}`;
495
+ }
496
+ }
497
+ class P {
498
+ constructor(e, t) {
499
+ this.currentValues = {}, this.container = e, this.onFilterChange = t;
500
+ }
501
+ render(e, t = {}) {
502
+ if (!e || e.length === 0) {
503
+ this.container.style.display = "none";
504
+ return;
505
+ }
506
+ this.container.style.display = "", this.currentValues = { ...t }, this.container.innerHTML = `<div class="ds-filter-bar">${e.map((r) => this.renderFilter(r, t[r.applyToField || r.filterId])).join("")}</div>`, this.attachListeners(e);
507
+ }
508
+ /**
509
+ * Populate a dropdown / multi-select with options fetched from the API.
510
+ * Called asynchronously after the filter bar is rendered.
511
+ */
512
+ updateOptions(e, t) {
513
+ const r = this.container.querySelector(
514
+ `select[data-filter-id="${e}"]`
515
+ );
516
+ if (!r) return;
517
+ const s = r.value;
518
+ r.innerHTML = '<option value="">All</option>' + t.map(
519
+ (i) => `<option value="${this.escAttr(i)}">${this.escText(i)}</option>`
520
+ ).join(""), s && t.includes(s) && (r.value = s);
521
+ }
522
+ /** Update the stored values without re-rendering (for programmatic setFilter) */
523
+ updateValues(e) {
524
+ Object.assign(this.currentValues, e), Object.entries(e).forEach(([t, r]) => {
525
+ const s = this.container.querySelector(`[data-filter-id="${t}"]`);
526
+ s && r !== void 0 && (s.value = String(r));
527
+ });
528
+ }
529
+ // ── Private ─────────────────────────────────────────────────────────────────
530
+ renderFilter(e, t) {
531
+ const r = `ds-filter-${e.filterId}`, s = `<label class="ds-filter-label" for="${r}">${e.label}</label>`;
532
+ switch (e.type) {
533
+ case "dropdown":
534
+ return `
535
+ <div class="ds-filter-item">
536
+ ${s}
537
+ <select class="ds-filter-select" id="${r}" data-filter-id="${e.filterId}">
538
+ <option value="">All</option>
539
+ ${(e.options || []).map((d) => `
540
+ <option value="${this.escAttr(d)}" ${t === d ? "selected" : ""}>
541
+ ${this.escText(d)}
542
+ </option>
543
+ `).join("")}
544
+ </select>
545
+ </div>`;
546
+ case "multi-select":
547
+ return `
548
+ <div class="ds-filter-item">
549
+ ${s}
550
+ <select class="ds-filter-select" id="${r}" data-filter-id="${e.filterId}" multiple size="1">
551
+ ${(e.options || []).map((d) => `
552
+ <option value="${this.escAttr(d)}">${this.escText(d)}</option>
553
+ `).join("")}
554
+ </select>
555
+ </div>`;
556
+ case "date-range":
557
+ const i = (t == null ? void 0 : t.from) || "", a = (t == null ? void 0 : t.to) || "";
558
+ return `
559
+ <div class="ds-filter-item">
560
+ ${s}
561
+ <div class="ds-date-range-inputs">
562
+ <input type="date" class="ds-filter-input"
563
+ data-filter-id="${e.filterId}" data-date-part="from"
564
+ value="${i}" />
565
+ <span>–</span>
566
+ <input type="date" class="ds-filter-input"
567
+ data-filter-id="${e.filterId}" data-date-part="to"
568
+ value="${a}" />
569
+ </div>
570
+ </div>`;
571
+ case "text-input":
572
+ return `
573
+ <div class="ds-filter-item">
574
+ ${s}
575
+ <input type="text" class="ds-filter-input" id="${r}"
576
+ data-filter-id="${e.filterId}"
577
+ value="${this.escAttr(String(t != null ? t : ""))}"
578
+ placeholder="Search..." />
579
+ </div>`;
580
+ case "number-input":
581
+ return `
582
+ <div class="ds-filter-item">
583
+ ${s}
584
+ <input type="number" class="ds-filter-input" id="${r}"
585
+ data-filter-id="${e.filterId}"
586
+ value="${this.escAttr(String(t != null ? t : ""))}" />
587
+ </div>`;
588
+ default:
589
+ return "";
590
+ }
591
+ }
592
+ attachListeners(e) {
593
+ const t = {};
594
+ this.container.querySelectorAll("[data-filter-id]").forEach((r) => {
595
+ const s = r.dataset.filterId, i = r.dataset.datePart, a = e.find((o) => o.filterId === s), d = () => {
596
+ let o;
597
+ if ((a == null ? void 0 : a.type) === "date-range" && i)
598
+ t[s] = t[s] || {}, t[s][i] = r.value, o = { ...t[s] };
599
+ else if ((a == null ? void 0 : a.type) === "multi-select") {
600
+ const l = r;
601
+ o = Array.from(l.selectedOptions).map((b) => b.value).filter(Boolean);
602
+ } else
603
+ o = r.value;
604
+ this.currentValues[s] = o, this.onFilterChange(s, o);
605
+ };
606
+ r.addEventListener(
607
+ (a == null ? void 0 : a.type) === "text-input" ? "input" : "change",
608
+ d
609
+ );
610
+ });
611
+ }
612
+ escAttr(e) {
613
+ return e.replace(/"/g, "&quot;").replace(/'/g, "&#39;");
614
+ }
615
+ escText(e) {
616
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
617
+ }
618
+ }
619
+ class O {
620
+ constructor(e, t) {
621
+ this.activePageId = "", this.activeTabId = "", this.activeFilters = {}, this.chartRenderers = /* @__PURE__ */ new Map(), this.tableRenderer = new R(), this.cardRenderer = new F(), this.isLoadingTab = !1, this.root = e, this.opts = t, this.activeFilters = { ...t.initialFilters };
622
+ }
623
+ // ── Initial full render ───────────────────────────────────────────────────
624
+ render(e) {
625
+ var t, r;
626
+ this.renderData = e, this.activePageId = e.activePage, this.activeTabId = e.activeTab, this.buildShell(), this.renderNavigation(), this.renderFilterBar(), this.renderGrid(), (r = (t = this.opts).onReady) == null || r.call(t);
627
+ }
628
+ // ── Called after lazy tab fetch completes ─────────────────────────────────
629
+ update(e) {
630
+ this.renderData = e, this.activePageId = e.activePage, this.activeTabId = e.activeTab, this.isLoadingTab = !1, this.syncNavHighlight(), this.updateFilterBar(), this.renderGrid();
631
+ }
632
+ goToPage(e) {
633
+ const t = this.getTabsForPage(e)[0];
634
+ t && this.triggerTabSwitch(e, t.id);
635
+ }
636
+ goToTab(e, t) {
637
+ this.triggerTabSwitch(e, t);
638
+ }
639
+ destroy() {
640
+ this.chartRenderers.forEach((e) => e.destroy()), this.chartRenderers.clear(), this.root.innerHTML = "";
641
+ }
642
+ exportPDF() {
643
+ const e = this.root.querySelector("#ds-canvas");
644
+ e && import("./html2canvas.esm-CzwMv54K.js").then(
645
+ ({ default: t }) => t(e, { scale: 2, useCORS: !0 }).then(
646
+ (r) => import("./jspdf.es.min-Ge0fRUwj.js").then((s) => s.j).then(({ jsPDF: s }) => {
647
+ const i = new s({ orientation: "landscape", unit: "px" });
648
+ i.addImage(r.toDataURL("image/png"), "PNG", 0, 0, i.internal.pageSize.width, 0), i.save(`${this.renderData.dashboard.name || "dashboard"}.pdf`);
649
+ })
650
+ )
651
+ );
652
+ }
653
+ exportCSV() {
654
+ this.renderData.components.forEach((e) => {
655
+ const t = this.renderData.data[e.id];
656
+ if (!(t != null && t.length) || !["table", "bar", "line", "area"].includes(e.type)) return;
657
+ const r = Object.keys(t[0]), s = [r.join(","), ...t.map(
658
+ (a) => r.map((d) => {
659
+ var o;
660
+ return JSON.stringify((o = a[d]) != null ? o : "");
661
+ }).join(",")
662
+ )].join(`
663
+ `);
664
+ Object.assign(document.createElement("a"), {
665
+ href: URL.createObjectURL(new Blob([s], { type: "text/csv" })),
666
+ download: `${e.title || e.id}.csv`
667
+ }).click();
668
+ });
669
+ }
670
+ updateFilterValues(e) {
671
+ var t;
672
+ Object.assign(this.activeFilters, e), (t = this.filterRenderer) == null || t.updateValues(e);
673
+ }
674
+ // ── Shell ─────────────────────────────────────────────────────────────────
675
+ buildShell() {
676
+ const e = this.opts.embedLevel, t = this.renderData.dashboard.pages, r = e === "dashboard" && t.length > 1, s = e === "page" || e === "dashboard";
677
+ this.root.innerHTML = `
678
+ ${this.opts.hideExport ? "" : '<div class="ds-toolbar" id="ds-toolbar"></div>'}
679
+ ${r ? '<nav class="ds-page-nav" id="ds-page-nav"></nav>' : ""}
680
+ ${s ? '<nav class="ds-page-nav" id="ds-tab-nav"></nav>' : ""}
681
+ <div id="ds-filter-container"></div>
682
+ <div class="ds-canvas" id="ds-canvas">
683
+ <div class="ds-grid" id="ds-grid"></div>
684
+ </div>
685
+ <div id="ds-tab-loading" style="display:none;position:absolute;inset:0;
686
+ background:rgba(255,255,255,0.5);align-items:center;justify-content:center;">
687
+ <div class="ds-embed-spinner"></div>
688
+ </div>
689
+ `, this.root.style.position = "relative", this.opts.hideExport || this.buildExportToolbar();
690
+ const i = this.root.querySelector("#ds-filter-container");
691
+ this.filterRenderer = new P(i, (a, d) => {
692
+ var b, c;
693
+ const o = this.renderData.filters.find((n) => n.filterId === a), l = (o == null ? void 0 : o.applyToField) || a;
694
+ this.activeFilters[l] = d, (c = (b = this.opts).onFilterChange) == null || c.call(b, this.activeFilters), this.opts.onTabSwitch(this.activePageId, this.activeTabId);
695
+ });
696
+ }
697
+ buildExportToolbar() {
698
+ var t, r;
699
+ const e = this.root.querySelector("#ds-toolbar");
700
+ e && (e.innerHTML = `
701
+ <button class="ds-toolbar-btn" id="ds-btn-pdf">⬇ PDF</button>
702
+ <button class="ds-toolbar-btn" id="ds-btn-csv">⬇ CSV</button>
703
+ `, (t = e.querySelector("#ds-btn-pdf")) == null || t.addEventListener("click", () => this.exportPDF()), (r = e.querySelector("#ds-btn-csv")) == null || r.addEventListener("click", () => this.exportCSV()));
704
+ }
705
+ // ── Navigation ────────────────────────────────────────────────────────────
706
+ renderNavigation() {
707
+ this.renderPageNav(), this.renderTabNav(this.activePageId);
708
+ }
709
+ renderPageNav() {
710
+ const e = this.root.querySelector("#ds-page-nav");
711
+ e && (e.innerHTML = this.renderData.dashboard.pages.map((t) => `
712
+ <button class="ds-page-tab ${t.pageId === this.activePageId ? "ds-active" : ""}"
713
+ data-page-id="${t.pageId}">${t.title}</button>
714
+ `).join(""), e.querySelectorAll(".ds-page-tab").forEach((t) => {
715
+ t.addEventListener("click", () => {
716
+ var s, i;
717
+ const r = t.dataset.pageId;
718
+ r === this.activePageId && !this.isLoadingTab || this.triggerTabSwitch(r, (i = (s = this.getTabsForPage(r)[0]) == null ? void 0 : s.id) != null ? i : "");
719
+ });
720
+ }));
721
+ }
722
+ renderTabNav(e) {
723
+ const t = this.root.querySelector("#ds-tab-nav");
724
+ t && this.buildTabButtons(t, e);
725
+ }
726
+ buildTabButtons(e, t) {
727
+ const r = this.getTabsForPage(t);
728
+ if (r.length <= 1) {
729
+ e.style.display = "none";
730
+ return;
731
+ }
732
+ e.style.display = "", e.innerHTML = r.map((s) => `
733
+ <button class="ds-page-tab ${s.id === this.activeTabId ? "ds-active" : ""}"
734
+ data-tab-id="${s.id}" data-page-id="${t}">${s.title}</button>
735
+ `).join(""), e.querySelectorAll(".ds-page-tab").forEach((s) => {
736
+ s.addEventListener("click", () => {
737
+ const i = s.dataset.tabId, a = s.dataset.pageId;
738
+ i === this.activeTabId && a === this.activePageId && !this.isLoadingTab || this.triggerTabSwitch(a, i);
739
+ });
740
+ });
741
+ }
742
+ /** Optimistically highlight new tab + show spinner, then fire callback */
743
+ triggerTabSwitch(e, t) {
744
+ this.isLoadingTab || !e || !t || (this.isLoadingTab = !0, this.activePageId = e, this.activeTabId = t, this.syncNavHighlight(), this.showTabSpinner(), this.opts.onTabSwitch(e, t));
745
+ }
746
+ syncNavHighlight() {
747
+ this.root.querySelectorAll("#ds-page-nav .ds-page-tab").forEach((t) => {
748
+ t.classList.toggle("ds-active", t.dataset.pageId === this.activePageId);
749
+ });
750
+ const e = this.root.querySelector("#ds-tab-nav");
751
+ e && this.buildTabButtons(e, this.activePageId);
752
+ }
753
+ // ── Filter bar ────────────────────────────────────────────────────────────
754
+ renderFilterBar() {
755
+ this.opts.hideFilters || (this.filterRenderer.render(this.renderData.filters, this.activeFilters), this.fetchFilterOptions());
756
+ }
757
+ updateFilterBar() {
758
+ this.opts.hideFilters || (this.filterRenderer.render(this.renderData.filters, this.activeFilters), this.fetchFilterOptions());
759
+ }
760
+ /**
761
+ * For every dropdown / multi-select filter whose options array is empty,
762
+ * call onFetchFilterOptions(filterId) and populate the select element.
763
+ * Mirrors: GET /dashboard-builder/{dashboardId}/filters/{filterId}/options
764
+ */
765
+ async fetchFilterOptions() {
766
+ if (this.opts.onFetchFilterOptions)
767
+ for (const e of this.renderData.filters) {
768
+ if (e.type !== "dropdown" && e.type !== "multi-select" || e.options && e.options.length > 0) continue;
769
+ const t = await this.opts.onFetchFilterOptions(e.filterId);
770
+ t.length > 0 && this.filterRenderer.updateOptions(e.filterId, t);
771
+ }
772
+ }
773
+ // ── Grid ──────────────────────────────────────────────────────────────────
774
+ renderGrid() {
775
+ this.hideTabSpinner();
776
+ const e = this.root.querySelector("#ds-grid");
777
+ if (!e) return;
778
+ this.chartRenderers.forEach((l) => l.destroy()), this.chartRenderers.clear();
779
+ const t = this.renderData.components;
780
+ if (!t.length) {
781
+ e.style.height = "200px", e.innerHTML = '<div class="ds-chart-empty" style="padding-top:80px;">No components on this tab.</div>';
782
+ return;
783
+ }
784
+ const r = e.clientWidth || this.root.clientWidth || 800, s = t.map((l) => l.position), i = s.length ? Math.min(...s.map((l) => l.y)) : 0, a = new I(r, i);
785
+ e.style.height = `${I.totalHeight(s)}px`, e.innerHTML = "";
786
+ const d = 36, o = [];
787
+ t.forEach((l) => {
788
+ const b = document.createElement("div");
789
+ b.className = "ds-component-wrapper", b.dataset.componentId = l.id, a.applyStyles(b, l.position), e.appendChild(b);
790
+ const c = a.toPx(l.position), n = Math.max(50, c.height - d);
791
+ o.push(this.renderComponent(b, l, n));
792
+ }), Promise.all(o).catch(() => {
793
+ });
794
+ }
795
+ async renderComponent(e, t, r) {
796
+ var a, d;
797
+ const s = this.renderData.data[t.id] || [], i = this.opts.theme;
798
+ switch (t.type) {
799
+ case "bar":
800
+ case "line":
801
+ case "pie":
802
+ case "donut":
803
+ case "area": {
804
+ const o = new L(e);
805
+ await o.render(t, s, i, r), this.chartRenderers.set(t.id, o);
806
+ break;
807
+ }
808
+ case "table":
809
+ this.tableRenderer.render(e, t, s);
810
+ break;
811
+ case "number-card":
812
+ this.cardRenderer.render(e, t, s);
813
+ break;
814
+ case "text": {
815
+ const o = Object.entries(((a = t.properties) == null ? void 0 : a.style) || {}).map(([l, b]) => `${l}:${b}`).join(";");
816
+ e.innerHTML = `<div style="${o};padding:8px;">${((d = t.properties) == null ? void 0 : d.content) || ""}</div>`;
817
+ break;
818
+ }
819
+ }
820
+ }
821
+ // ── Spinner ───────────────────────────────────────────────────────────────
822
+ showTabSpinner() {
823
+ const e = this.root.querySelector("#ds-tab-loading"), t = this.root.querySelector("#ds-grid");
824
+ e && (e.style.display = "flex"), t && (t.style.opacity = "0.35");
825
+ }
826
+ hideTabSpinner() {
827
+ const e = this.root.querySelector("#ds-tab-loading"), t = this.root.querySelector("#ds-grid");
828
+ e && (e.style.display = "none"), t && (t.style.opacity = "1");
829
+ }
830
+ // ── Helpers ───────────────────────────────────────────────────────────────
831
+ getTabsForPage(e) {
832
+ var t, r;
833
+ return (r = (t = this.renderData.dashboard.pages.find((s) => s.pageId === e)) == null ? void 0 : t.tabs) != null ? r : [];
834
+ }
835
+ }
836
+ class j {
837
+ constructor(e, t) {
838
+ this.chartRenderer = null, this.tableRenderer = new R(), this.cardRenderer = new F(), this.root = e, this.theme = t;
839
+ }
840
+ render(e, t) {
841
+ const r = e.components[0];
842
+ if (!r) {
843
+ this.root.innerHTML = `<div class="ds-embed-error">
844
+ <div class="ds-embed-error-icon">⚠</div>
845
+ <div>Component not found.</div>
846
+ </div>`;
847
+ return;
848
+ }
849
+ const s = e.data[r.id] || [];
850
+ this.renderComponent(r, s), t == null || t();
851
+ }
852
+ update(e) {
853
+ const t = e.components[0];
854
+ if (!t) return;
855
+ const r = e.data[t.id] || [];
856
+ switch (t.type) {
857
+ case "bar":
858
+ case "line":
859
+ case "pie":
860
+ case "donut":
861
+ case "area":
862
+ this.chartRenderer ? this.chartRenderer.update(t, r, this.theme) : this.renderComponent(t, r);
863
+ break;
864
+ case "table":
865
+ this.tableRenderer.render(this.root, t, r);
866
+ break;
867
+ case "number-card":
868
+ this.cardRenderer.render(this.root, t, r);
869
+ break;
870
+ }
871
+ }
872
+ destroy() {
873
+ var e;
874
+ (e = this.chartRenderer) == null || e.destroy(), this.chartRenderer = null, this.root.innerHTML = "";
875
+ }
876
+ renderComponent(e, t) {
877
+ var r;
878
+ switch (e.type) {
879
+ case "bar":
880
+ case "line":
881
+ case "pie":
882
+ case "donut":
883
+ case "area":
884
+ (r = this.chartRenderer) == null || r.destroy(), this.chartRenderer = new L(this.root), this.chartRenderer.render(e, t, this.theme);
885
+ break;
886
+ case "table":
887
+ this.tableRenderer.render(this.root, e, t);
888
+ break;
889
+ case "number-card":
890
+ this.cardRenderer.render(this.root, e, t);
891
+ break;
892
+ default:
893
+ this.root.innerHTML = `<div class="ds-chart-empty">Unsupported component type: ${e.type}</div>`;
894
+ }
895
+ }
896
+ }
897
+ let S = !1;
898
+ function M() {
899
+ if (!(S || typeof document == "undefined")) {
900
+ S = !0;
901
+ try {
902
+ const p = `/* ─────────────────────────────────────────────────────────────────────────────
903
+ Deepspot SDK — Base Embed Styles
904
+ Injected once into the host page's <head> by the SDK.
905
+ All selectors are scoped under .ds-embed-* to avoid leaking into host styles.
906
+ ───────────────────────────────────────────────────────────────────────────── */
907
+
908
+ /* ── Reset inside SDK containers ──────────────────────────────────────────── */
909
+ .ds-embed-root *,
910
+ .ds-embed-root *::before,
911
+ .ds-embed-root *::after {
912
+ box-sizing: border-box;
913
+ margin: 0;
914
+ padding: 0;
915
+ }
916
+
917
+ /* ── Root container ────────────────────────────────────────────────────────── */
918
+ .ds-embed-root {
919
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
920
+ Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
921
+ font-size: 14px;
922
+ line-height: 1.5;
923
+ width: 100%;
924
+ height: 100%;
925
+ overflow: auto;
926
+ position: relative;
927
+ }
928
+
929
+ .ds-embed-root.ds-theme-light {
930
+ background: #ffffff;
931
+ color: #111827;
932
+ }
933
+
934
+ .ds-embed-root.ds-theme-dark {
935
+ background: #0f172a;
936
+ color: #f1f5f9;
937
+ }
938
+
939
+ /* ── Loading state ─────────────────────────────────────────────────────────── */
940
+ .ds-embed-loading {
941
+ display: flex;
942
+ flex-direction: column;
943
+ align-items: center;
944
+ justify-content: center;
945
+ height: 100%;
946
+ min-height: 200px;
947
+ gap: 12px;
948
+ color: #6b7280;
949
+ }
950
+
951
+ .ds-embed-spinner {
952
+ width: 36px;
953
+ height: 36px;
954
+ border: 3px solid #e5e7eb;
955
+ border-top-color: #6366f1;
956
+ border-radius: 50%;
957
+ animation: ds-spin 0.8s linear infinite;
958
+ }
959
+
960
+ @keyframes ds-spin {
961
+ to { transform: rotate(360deg); }
962
+ }
963
+
964
+ .ds-embed-loading-text {
965
+ font-size: 13px;
966
+ }
967
+
968
+ /* ── Error state ───────────────────────────────────────────────────────────── */
969
+ .ds-embed-error {
970
+ display: flex;
971
+ flex-direction: column;
972
+ align-items: center;
973
+ justify-content: center;
974
+ height: 100%;
975
+ min-height: 200px;
976
+ gap: 8px;
977
+ padding: 24px;
978
+ text-align: center;
979
+ color: #ef4444;
980
+ }
981
+
982
+ .ds-embed-error-icon {
983
+ font-size: 32px;
984
+ }
985
+
986
+ .ds-embed-error-message {
987
+ font-size: 13px;
988
+ color: #6b7280;
989
+ }
990
+
991
+ /* ── Filter bar ────────────────────────────────────────────────────────────── */
992
+ .ds-filter-bar {
993
+ display: flex;
994
+ flex-wrap: wrap;
995
+ align-items: center;
996
+ gap: 10px;
997
+ padding: 10px 16px;
998
+ border-bottom: 1px solid #e5e7eb;
999
+ background: inherit;
1000
+ }
1001
+
1002
+ .ds-theme-dark .ds-filter-bar {
1003
+ border-bottom-color: #1e293b;
1004
+ }
1005
+
1006
+ .ds-filter-item {
1007
+ display: flex;
1008
+ flex-direction: column;
1009
+ gap: 4px;
1010
+ }
1011
+
1012
+ .ds-filter-label {
1013
+ font-size: 11px;
1014
+ font-weight: 600;
1015
+ text-transform: uppercase;
1016
+ letter-spacing: 0.05em;
1017
+ color: #6b7280;
1018
+ }
1019
+
1020
+ .ds-filter-select,
1021
+ .ds-filter-input {
1022
+ height: 32px;
1023
+ padding: 0 10px;
1024
+ border: 1px solid #d1d5db;
1025
+ border-radius: 6px;
1026
+ font-size: 13px;
1027
+ background: #ffffff;
1028
+ color: #111827;
1029
+ cursor: pointer;
1030
+ min-width: 130px;
1031
+ }
1032
+
1033
+ .ds-theme-dark .ds-filter-select,
1034
+ .ds-theme-dark .ds-filter-input {
1035
+ background: #1e293b;
1036
+ border-color: #334155;
1037
+ color: #f1f5f9;
1038
+ }
1039
+
1040
+ .ds-filter-select:focus,
1041
+ .ds-filter-input:focus {
1042
+ outline: none;
1043
+ border-color: #6366f1;
1044
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
1045
+ }
1046
+
1047
+ .ds-date-range-inputs {
1048
+ display: flex;
1049
+ gap: 6px;
1050
+ align-items: center;
1051
+ }
1052
+
1053
+ .ds-date-range-inputs input[type="date"] {
1054
+ height: 32px;
1055
+ padding: 0 8px;
1056
+ border: 1px solid #d1d5db;
1057
+ border-radius: 6px;
1058
+ font-size: 13px;
1059
+ background: #ffffff;
1060
+ color: #111827;
1061
+ }
1062
+
1063
+ .ds-theme-dark .ds-date-range-inputs input[type="date"] {
1064
+ background: #1e293b;
1065
+ border-color: #334155;
1066
+ color: #f1f5f9;
1067
+ }
1068
+
1069
+ /* ── Page / Tab navigation ─────────────────────────────────────────────────── */
1070
+ .ds-page-nav {
1071
+ display: flex;
1072
+ gap: 4px;
1073
+ padding: 8px 16px 0;
1074
+ border-bottom: 1px solid #e5e7eb;
1075
+ overflow-x: auto;
1076
+ }
1077
+
1078
+ .ds-theme-dark .ds-page-nav {
1079
+ border-bottom-color: #1e293b;
1080
+ }
1081
+
1082
+ .ds-page-tab {
1083
+ padding: 6px 16px;
1084
+ font-size: 13px;
1085
+ font-weight: 500;
1086
+ border: none;
1087
+ border-bottom: 2px solid transparent;
1088
+ background: transparent;
1089
+ color: #6b7280;
1090
+ cursor: pointer;
1091
+ white-space: nowrap;
1092
+ transition: color 0.15s, border-color 0.15s;
1093
+ }
1094
+
1095
+ .ds-page-tab:hover {
1096
+ color: #111827;
1097
+ }
1098
+
1099
+ .ds-theme-dark .ds-page-tab:hover {
1100
+ color: #f1f5f9;
1101
+ }
1102
+
1103
+ .ds-page-tab.ds-active {
1104
+ color: #6366f1;
1105
+ border-bottom-color: #6366f1;
1106
+ }
1107
+
1108
+ /* ── Grid canvas ───────────────────────────────────────────────────────────── */
1109
+ .ds-canvas {
1110
+ position: relative;
1111
+ width: 100%;
1112
+ /* No horizontal padding — grid.clientWidth must equal container width so
1113
+ GridLayout.colWidth = containerWidth/24 matches the dashboard builder.
1114
+ 16px on each side was shrinking the grid by 32px and misaligning positions. */
1115
+ padding: 8px 0 16px;
1116
+ overflow-x: hidden;
1117
+ }
1118
+
1119
+ .ds-grid {
1120
+ position: relative;
1121
+ width: 100%;
1122
+ /* Clip absolutely-positioned children to the grid boundary so rounding
1123
+ errors or miscalculated widths don't create a horizontal scrollbar. */
1124
+ overflow: hidden;
1125
+ }
1126
+
1127
+ .ds-component-wrapper {
1128
+ position: absolute;
1129
+ overflow: hidden;
1130
+ }
1131
+
1132
+ /* ── Chart component ───────────────────────────────────────────────────────── */
1133
+ .ds-chart-card {
1134
+ width: 100%;
1135
+ height: 100%;
1136
+ background: #ffffff;
1137
+ border: 1px solid #e5e7eb;
1138
+ border-radius: 10px;
1139
+ overflow: hidden;
1140
+ display: flex;
1141
+ flex-direction: column;
1142
+ }
1143
+
1144
+ .ds-theme-dark .ds-chart-card {
1145
+ background: #1e293b;
1146
+ border-color: #334155;
1147
+ }
1148
+
1149
+ .ds-chart-title {
1150
+ padding: 10px 14px 4px;
1151
+ font-size: 13px;
1152
+ font-weight: 600;
1153
+ color: #374151;
1154
+ flex-shrink: 0;
1155
+ }
1156
+
1157
+ .ds-theme-dark .ds-chart-title {
1158
+ color: #e2e8f0;
1159
+ }
1160
+
1161
+ .ds-chart-body {
1162
+ flex: 1;
1163
+ min-height: 0;
1164
+ }
1165
+
1166
+ .ds-chart-empty {
1167
+ display: flex;
1168
+ align-items: center;
1169
+ justify-content: center;
1170
+ height: 100%;
1171
+ color: #9ca3af;
1172
+ font-size: 13px;
1173
+ }
1174
+
1175
+ /* ── Table component ───────────────────────────────────────────────────────── */
1176
+ .ds-table-card {
1177
+ width: 100%;
1178
+ height: 100%;
1179
+ background: #ffffff;
1180
+ border: 1px solid #e5e7eb;
1181
+ border-radius: 10px;
1182
+ overflow: hidden;
1183
+ display: flex;
1184
+ flex-direction: column;
1185
+ }
1186
+
1187
+ .ds-theme-dark .ds-table-card {
1188
+ background: #1e293b;
1189
+ border-color: #334155;
1190
+ }
1191
+
1192
+ .ds-table-title {
1193
+ padding: 10px 14px 4px;
1194
+ font-size: 13px;
1195
+ font-weight: 600;
1196
+ color: #374151;
1197
+ flex-shrink: 0;
1198
+ }
1199
+
1200
+ .ds-theme-dark .ds-table-title {
1201
+ color: #e2e8f0;
1202
+ }
1203
+
1204
+ .ds-table-scroll {
1205
+ flex: 1;
1206
+ overflow: auto;
1207
+ }
1208
+
1209
+ .ds-table {
1210
+ width: 100%;
1211
+ border-collapse: collapse;
1212
+ font-size: 12px;
1213
+ }
1214
+
1215
+ .ds-table th {
1216
+ position: sticky;
1217
+ top: 0;
1218
+ padding: 8px 12px;
1219
+ text-align: left;
1220
+ font-weight: 600;
1221
+ font-size: 11px;
1222
+ text-transform: uppercase;
1223
+ letter-spacing: 0.05em;
1224
+ background: #f9fafb;
1225
+ color: #6b7280;
1226
+ border-bottom: 1px solid #e5e7eb;
1227
+ white-space: nowrap;
1228
+ }
1229
+
1230
+ .ds-theme-dark .ds-table th {
1231
+ background: #0f172a;
1232
+ color: #94a3b8;
1233
+ border-bottom-color: #334155;
1234
+ }
1235
+
1236
+ .ds-table td {
1237
+ padding: 8px 12px;
1238
+ border-bottom: 1px solid #f3f4f6;
1239
+ color: #374151;
1240
+ max-width: 200px;
1241
+ overflow: hidden;
1242
+ text-overflow: ellipsis;
1243
+ white-space: nowrap;
1244
+ }
1245
+
1246
+ .ds-theme-dark .ds-table td {
1247
+ color: #cbd5e1;
1248
+ border-bottom-color: #1e293b;
1249
+ }
1250
+
1251
+ .ds-table tr:hover td {
1252
+ background: #f9fafb;
1253
+ }
1254
+
1255
+ .ds-theme-dark .ds-table tr:hover td {
1256
+ background: #1e293b;
1257
+ }
1258
+
1259
+ /* ── KPI Number Card ───────────────────────────────────────────────────────── */
1260
+ .ds-card {
1261
+ width: 100%;
1262
+ height: 100%;
1263
+ border: 1px solid #e5e7eb;
1264
+ border-radius: 10px;
1265
+ display: flex;
1266
+ flex-direction: column;
1267
+ align-items: center;
1268
+ justify-content: center;
1269
+ padding: 16px;
1270
+ text-align: center;
1271
+ background: #ffffff;
1272
+ }
1273
+
1274
+ .ds-theme-dark .ds-card {
1275
+ background: #1e293b;
1276
+ border-color: #334155;
1277
+ }
1278
+
1279
+ .ds-card-label {
1280
+ font-size: 12px;
1281
+ font-weight: 500;
1282
+ color: #6b7280;
1283
+ margin-bottom: 6px;
1284
+ text-transform: uppercase;
1285
+ letter-spacing: 0.05em;
1286
+ }
1287
+
1288
+ .ds-card-value {
1289
+ font-size: 32px;
1290
+ font-weight: 700;
1291
+ color: #111827;
1292
+ line-height: 1.1;
1293
+ }
1294
+
1295
+ .ds-theme-dark .ds-card-value {
1296
+ color: #f1f5f9;
1297
+ }
1298
+
1299
+ /* ── Export toolbar ────────────────────────────────────────────────────────── */
1300
+ .ds-toolbar {
1301
+ display: flex;
1302
+ justify-content: flex-end;
1303
+ padding: 8px 16px 0;
1304
+ gap: 8px;
1305
+ }
1306
+
1307
+ .ds-toolbar-btn {
1308
+ height: 30px;
1309
+ padding: 0 12px;
1310
+ border: 1px solid #d1d5db;
1311
+ border-radius: 6px;
1312
+ font-size: 12px;
1313
+ font-weight: 500;
1314
+ background: #ffffff;
1315
+ color: #374151;
1316
+ cursor: pointer;
1317
+ display: flex;
1318
+ align-items: center;
1319
+ gap: 5px;
1320
+ transition: background 0.15s, border-color 0.15s;
1321
+ }
1322
+
1323
+ .ds-toolbar-btn:hover {
1324
+ background: #f9fafb;
1325
+ border-color: #9ca3af;
1326
+ }
1327
+
1328
+ .ds-theme-dark .ds-toolbar-btn {
1329
+ background: #1e293b;
1330
+ border-color: #334155;
1331
+ color: #e2e8f0;
1332
+ }
1333
+
1334
+ .ds-theme-dark .ds-toolbar-btn:hover {
1335
+ background: #0f172a;
1336
+ }
1337
+ `, e = document.createElement("style");
1338
+ e.id = "deepspot-sdk-styles", e.textContent = p, document.head.appendChild(e);
1339
+ } catch (p) {
1340
+ }
1341
+ }
1342
+ }
1343
+ function C(p) {
1344
+ if (typeof p == "string") {
1345
+ const e = document.querySelector(p);
1346
+ if (!e) throw new Error(`Deepspot SDK: container "${p}" not found in DOM`);
1347
+ return e;
1348
+ }
1349
+ return p;
1350
+ }
1351
+ class K {
1352
+ constructor(e) {
1353
+ if (!e.apiKey) throw new Error("Deepspot SDK: apiKey is required");
1354
+ if (!e.baseUrl) throw new Error("Deepspot SDK: baseUrl is required");
1355
+ this.apiClient = new A(e.baseUrl, e.apiKey), M();
1356
+ }
1357
+ // ── Embed full dashboard ────────────────────────────────────────────────────
1358
+ async embedDashboard(e) {
1359
+ var a, d, o, l, b;
1360
+ const t = C(e.container), r = (a = e.embedLevel) != null ? a : "dashboard", s = (d = e.theme) != null ? d : "light";
1361
+ t.style.height = e.height || "600px", t.style.display = "block";
1362
+ const i = this.createRoot(t, s);
1363
+ this.showLoading(i);
1364
+ try {
1365
+ const c = await this.apiClient.getEmbedToken({
1366
+ dashboardId: e.dashboardId,
1367
+ embedType: "dashboard",
1368
+ embedLevel: r,
1369
+ userId: e.userId,
1370
+ tenantId: e.tenantId
1371
+ }), n = await this.apiClient.getDashboardRender(
1372
+ e.dashboardId,
1373
+ c,
1374
+ {
1375
+ embedLevel: r,
1376
+ pageId: e.pageId,
1377
+ tabId: e.tabId,
1378
+ filters: e.filters || {}
1379
+ }
1380
+ );
1381
+ let h;
1382
+ const f = new O(i, {
1383
+ embedLevel: r,
1384
+ theme: s,
1385
+ hideFilters: (o = e.hideFilters) != null ? o : !1,
1386
+ hideExport: (l = e.hideExport) != null ? l : !1,
1387
+ initialFilters: e.filters || {},
1388
+ onFilterChange: e.onFilterChange,
1389
+ onReady: e.onReady,
1390
+ onFetchFilterOptions: (g) => this.apiClient.getFilterOptions(e.dashboardId, g, c),
1391
+ onTabSwitch: async (g, m) => {
1392
+ var u, y, k;
1393
+ (u = e.onTabSwitch) == null || u.call(e, g, m);
1394
+ try {
1395
+ const v = await this.apiClient.getDashboardRender(
1396
+ e.dashboardId,
1397
+ c,
1398
+ {
1399
+ embedLevel: r,
1400
+ pageId: g,
1401
+ tabId: m,
1402
+ filters: (k = (y = h == null ? void 0 : h.getActiveFilters()) != null ? y : e.filters) != null ? k : {}
1403
+ }
1404
+ );
1405
+ f.update(v);
1406
+ } catch (v) {
1407
+ console.error("Deepspot SDK: tab fetch failed", v);
1408
+ }
1409
+ }
1410
+ });
1411
+ return f.render(n), h = new $({
1412
+ dashboardId: e.dashboardId,
1413
+ token: c,
1414
+ embedType: "dashboard",
1415
+ embedLevel: r,
1416
+ activePageId: n.activePage,
1417
+ activeTabId: n.activeTab,
1418
+ activeFilters: e.filters || {},
1419
+ apiClient: this.apiClient,
1420
+ renderer: f,
1421
+ onFilterChange: e.onFilterChange
1422
+ }), h;
1423
+ } catch (c) {
1424
+ throw this.showError(i, (c == null ? void 0 : c.message) || "Failed to load dashboard"), (b = e.onError) == null || b.call(e, (c == null ? void 0 : c.message) || "Failed to load dashboard"), c;
1425
+ }
1426
+ }
1427
+ // ── Embed single report (component) ────────────────────────────────────────
1428
+ async embedReport(e) {
1429
+ var s;
1430
+ const t = C(e.container);
1431
+ t.style.height = e.height || "400px", t.style.display = "block";
1432
+ const r = this.createRoot(t, e.theme || "light");
1433
+ this.showLoading(r);
1434
+ try {
1435
+ const i = await this.apiClient.getEmbedToken({
1436
+ dashboardId: e.dashboardId,
1437
+ embedType: "report",
1438
+ componentId: e.componentId,
1439
+ userId: e.userId,
1440
+ tenantId: e.tenantId
1441
+ }), a = await this.apiClient.getReportRender(
1442
+ e.dashboardId,
1443
+ e.componentId,
1444
+ i,
1445
+ { filters: e.filters || {} }
1446
+ ), d = new j(r, e.theme || "light");
1447
+ return d.render(a, e.onReady), new $({
1448
+ dashboardId: e.dashboardId,
1449
+ componentId: e.componentId,
1450
+ token: i,
1451
+ embedType: "report",
1452
+ activeFilters: e.filters || {},
1453
+ apiClient: this.apiClient,
1454
+ renderer: d
1455
+ });
1456
+ } catch (i) {
1457
+ throw this.showError(r, (i == null ? void 0 : i.message) || "Failed to load report"), (s = e.onError) == null || s.call(e, (i == null ? void 0 : i.message) || "Failed to load report"), i;
1458
+ }
1459
+ }
1460
+ // ── Private helpers ─────────────────────────────────────────────────────────
1461
+ createRoot(e, t) {
1462
+ e.innerHTML = "";
1463
+ const r = document.createElement("div");
1464
+ return r.className = `ds-embed-root ds-theme-${t}`, r.style.width = "100%", r.style.height = "100%", e.appendChild(r), r;
1465
+ }
1466
+ showLoading(e) {
1467
+ e.innerHTML = `
1468
+ <div class="ds-embed-loading">
1469
+ <div class="ds-embed-spinner"></div>
1470
+ <div class="ds-embed-loading-text">Loading…</div>
1471
+ </div>
1472
+ `;
1473
+ }
1474
+ showError(e, t) {
1475
+ e.innerHTML = `
1476
+ <div class="ds-embed-error">
1477
+ <div class="ds-embed-error-icon">⚠</div>
1478
+ <div>${t}</div>
1479
+ <div class="ds-embed-error-message">Check your SDK configuration.</div>
1480
+ </div>
1481
+ `;
1482
+ }
1483
+ }
1484
+ export {
1485
+ K as DeepspotSDK
1486
+ };