deepspotscreen-sdk 0.1.1 → 0.2.0

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.
@@ -0,0 +1,4490 @@
1
+ class ie {
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 o, i, a, f;
8
+ const t = [
9
+ e.dashboardId,
10
+ e.embedType,
11
+ (o = e.embedLevel) != null ? o : "",
12
+ (i = e.componentId) != null ? i : "",
13
+ (a = e.userId) != null ? a : ""
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: (f = e.embedLevel) != null ? f : "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 o;
37
+ const s = new URLSearchParams({
38
+ embedType: "dashboard",
39
+ embedLevel: (o = r.embedLevel) != null ? o : "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(([i, a]) => {
42
+ a != null && a !== "" && s.set(`filter[${i}]`, Array.isArray(a) ? a.join(",") : String(a));
43
+ }), r.filterOperators && Object.entries(r.filterOperators).forEach(([i, a]) => {
44
+ a && s.set(`filterOp[${i}]`, a);
45
+ }), r.localFilters && Object.entries(r.localFilters).forEach(([i, { value: a, operator: f }]) => {
46
+ const h = Array.isArray(a) ? a.join(",") : String(a);
47
+ h && (s.set(`localFilter[${i}]`, h), f && s.set(`localFilterOp[${i}]`, f));
48
+ }), this.get(
49
+ `/dashboard-builder/embed/${e}/render?${s}`,
50
+ { "x-embed-token": t }
51
+ );
52
+ }
53
+ // ── Single Report Render ────────────────────────────────────────────────────
54
+ async getReportRender(e, t, r, s = {}) {
55
+ const o = new URLSearchParams({
56
+ embedType: "report",
57
+ embedLevel: "report",
58
+ componentId: t
59
+ });
60
+ return s.filters && Object.entries(s.filters).forEach(([i, a]) => {
61
+ a != null && a !== "" && o.set(`filter[${i}]`, Array.isArray(a) ? a.join(",") : String(a));
62
+ }), this.get(
63
+ `/dashboard-builder/embed/${e}/render?${o}`,
64
+ { "x-embed-token": r }
65
+ );
66
+ }
67
+ // ── API Key Management ──────────────────────────────────────────────────────
68
+ async createApiKey(e, t) {
69
+ return this.post("/dashboard-builder/api-keys", e, {
70
+ Authorization: `Bearer ${t}`
71
+ });
72
+ }
73
+ async listApiKeys(e) {
74
+ return this.get("/dashboard-builder/api-keys", {
75
+ Authorization: `Bearer ${e}`
76
+ });
77
+ }
78
+ async deleteApiKey(e, t) {
79
+ await this.delete(`/dashboard-builder/api-keys/${e}`, {
80
+ Authorization: `Bearer ${t}`
81
+ });
82
+ }
83
+ // ── Paginated Table Data ────────────────────────────────────────────────────
84
+ /**
85
+ * Fetches one page of data for an embedded data-table component.
86
+ * Called by TableRenderer whenever the user navigates to a different page.
87
+ * Mirrors: GET /dashboard-builder/embed/:dashboardId/table/:componentId/data
88
+ */
89
+ async getTablePage(e, t, r, s, o) {
90
+ return this.get(
91
+ `/dashboard-builder/embed/${e}/table/${t}/data?page=${r}&pageSize=${s}`,
92
+ { "x-embed-token": o }
93
+ );
94
+ }
95
+ // ── Filter Options ──────────────────────────────────────────────────────────
96
+ /**
97
+ * Fetches distinct option values for a dropdown / multi-select filter.
98
+ * Mirrors the main dashboard call:
99
+ * GET /dashboard-builder/{dashboardId}/filters/{filterId}/options
100
+ * The embed token is sent so the backend can validate the caller has
101
+ * access to this dashboard without requiring an admin JWT.
102
+ */
103
+ async getFilterOptions(e, t, r) {
104
+ try {
105
+ const s = await this.get(
106
+ `/dashboard-builder/embed/${e}/filters/${t}/options`,
107
+ { "x-embed-token": r }
108
+ );
109
+ 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 : [];
110
+ } catch (s) {
111
+ return [];
112
+ }
113
+ }
114
+ clearTokenCache() {
115
+ this.tokenCache.clear();
116
+ }
117
+ // ── Private helpers ─────────────────────────────────────────────────────────
118
+ async get(e, t = {}) {
119
+ const r = await fetch(`${this.baseUrl}${e}`, {
120
+ method: "GET",
121
+ headers: { "Content-Type": "application/json", ...t }
122
+ });
123
+ return this.handleResponse(r);
124
+ }
125
+ async post(e, t, r = {}) {
126
+ const s = await fetch(`${this.baseUrl}${e}`, {
127
+ method: "POST",
128
+ headers: { "Content-Type": "application/json", ...r },
129
+ body: JSON.stringify(t)
130
+ });
131
+ return this.handleResponse(s);
132
+ }
133
+ async delete(e, t = {}) {
134
+ const r = await fetch(`${this.baseUrl}${e}`, {
135
+ method: "DELETE",
136
+ headers: { "Content-Type": "application/json", ...t }
137
+ });
138
+ if (!r.ok) throw new Error(`Deepspot SDK: DELETE ${e} failed (${r.status})`);
139
+ }
140
+ async handleResponse(e) {
141
+ if (!e.ok) {
142
+ let r = `HTTP ${e.status}`;
143
+ try {
144
+ const s = await e.json();
145
+ r = (s == null ? void 0 : s.message) || (s == null ? void 0 : s.error) || r;
146
+ } catch (s) {
147
+ }
148
+ throw new Error(`Deepspot SDK: ${r}`);
149
+ }
150
+ const t = await e.json();
151
+ return (t == null ? void 0 : t.data) !== void 0 ? t.data : t;
152
+ }
153
+ }
154
+ class Z {
155
+ constructor(e) {
156
+ var t, r;
157
+ 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 : "";
158
+ }
159
+ /** Returns a copy of the currently applied filters */
160
+ getActiveFilters() {
161
+ return { ...this.filters };
162
+ }
163
+ /** Apply a single filter and re-fetch data */
164
+ setFilter(e, t) {
165
+ this.filters[e] = t, this.scheduleRefresh();
166
+ }
167
+ /** Apply multiple filters at once and re-fetch */
168
+ setFilters(e) {
169
+ Object.assign(this.filters, e), this.scheduleRefresh();
170
+ }
171
+ /** v2 alias for setFilter — sets a global filter and re-fetches data */
172
+ setGlobalFilter(e, t) {
173
+ this.setFilter(e, t);
174
+ }
175
+ /** v2: clear all active filters and re-fetch data */
176
+ clearFilters() {
177
+ this.filters = {}, this.scheduleRefresh();
178
+ }
179
+ /** Navigate to a different page (dashboard embed only) */
180
+ goToPage(e) {
181
+ this.opts.embedType === "dashboard" && (this.activePageId = e, this.opts.renderer.goToPage(e));
182
+ }
183
+ /** Navigate to a specific tab on a specific page */
184
+ goToTab(e, t) {
185
+ this.opts.embedType === "dashboard" && (this.activePageId = e, this.activeTabId = t, this.opts.renderer.goToTab(e, t));
186
+ }
187
+ /** Force re-fetch all data from backend */
188
+ async refresh() {
189
+ if (!this.destroyed)
190
+ try {
191
+ const e = await this.fetchData();
192
+ this.opts.renderer.update(e);
193
+ } catch (e) {
194
+ console.error("Deepspot SDK: refresh failed", e);
195
+ }
196
+ }
197
+ /** Export PDF (dashboard embed only) */
198
+ exportPDF() {
199
+ this.opts.embedType === "dashboard" && this.opts.renderer.exportPDF();
200
+ }
201
+ /** Remove the embed and clean up */
202
+ destroy() {
203
+ this.destroyed = !0, this.refreshTimer && clearTimeout(this.refreshTimer), this.opts.renderer.destroy(), this.opts.apiClient.clearTokenCache();
204
+ }
205
+ // ── Private ─────────────────────────────────────────────────────────────────
206
+ /** Debounce rapid filter changes into a single fetch */
207
+ scheduleRefresh() {
208
+ this.refreshTimer && clearTimeout(this.refreshTimer), this.refreshTimer = setTimeout(() => this.refresh(), 300);
209
+ }
210
+ async fetchData() {
211
+ const { apiClient: e, dashboardId: t, componentId: r, token: s, embedType: o, embedLevel: i } = this.opts;
212
+ return o === "report" && r ? e.getReportRender(t, r, s, {
213
+ filters: this.filters
214
+ }) : e.getDashboardRender(t, s, {
215
+ embedLevel: i,
216
+ pageId: this.activePageId || void 0,
217
+ tabId: this.activeTabId || void 0,
218
+ filters: this.filters
219
+ });
220
+ }
221
+ }
222
+ const ae = 24, j = 10, V = 12;
223
+ class Q {
224
+ /**
225
+ * @param containerWidth Actual pixel width of the grid container.
226
+ * @param minY Minimum y value across all components (normalises
227
+ * the grid so the topmost component starts at top=6px).
228
+ */
229
+ constructor(e, t = 0) {
230
+ this.containerWidth = e, this.minY = t;
231
+ }
232
+ /** Pixel width of one column unit */
233
+ get colWidth() {
234
+ return this.containerWidth / ae;
235
+ }
236
+ /** Convert grid position {x,y,w,h} → CSS absolute pixel values */
237
+ toPx(e) {
238
+ const t = this.colWidth, r = V / 2;
239
+ return {
240
+ left: e.x * t + r,
241
+ top: (e.y - this.minY) * j + r,
242
+ width: e.w * t - V,
243
+ height: e.h * j - V
244
+ };
245
+ }
246
+ /** Total pixel height required to fit all positioned components */
247
+ static totalHeight(e) {
248
+ if (!e.length) return 400;
249
+ const t = Math.min(...e.map((s) => s.y));
250
+ return (Math.max(...e.map((s) => s.y + s.h)) - t) * j + 40;
251
+ }
252
+ /** Apply absolute positioning styles directly to a DOM element */
253
+ applyStyles(e, t) {
254
+ const r = this.toPx(t);
255
+ 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`;
256
+ }
257
+ }
258
+ let q = null;
259
+ function de() {
260
+ return q || (q = import("./apexcharts.common-k3hLWpB8.js").then((P) => P.a)), q;
261
+ }
262
+ class re {
263
+ constructor(e) {
264
+ this.chart = null, this.container = e;
265
+ }
266
+ async render(e, t, r, s) {
267
+ if (!t || t.length === 0) {
268
+ this.renderEmpty(e.title);
269
+ return;
270
+ }
271
+ const o = this.buildOptions(e, t, r, s);
272
+ if (!o) {
273
+ this.renderEmpty(e.title);
274
+ return;
275
+ }
276
+ this.container.innerHTML = `
277
+ <div class="ds-chart-card">
278
+ <div class="ds-chart-title">${e.title || ""}</div>
279
+ <div class="ds-chart-body" id="ds-chart-body-${e.id}"></div>
280
+ </div>
281
+ `;
282
+ const i = this.container.querySelector(`#ds-chart-body-${e.id}`);
283
+ if (i)
284
+ try {
285
+ this.chart && this.chart.destroy();
286
+ const { default: a } = await de();
287
+ this.chart = new a(i, o), this.chart.render();
288
+ } catch (a) {
289
+ console.error("[Deepspot SDK] ChartRenderer error:", a), i.innerHTML = '<div class="ds-chart-empty">Chart render error</div>';
290
+ }
291
+ }
292
+ async update(e, t, r) {
293
+ if (!this.chart || !t || t.length === 0) {
294
+ await this.render(e, t, r);
295
+ return;
296
+ }
297
+ const { series: s, categories: o } = this.extractSeriesAndCategories(e, t);
298
+ try {
299
+ this.chart.updateOptions({
300
+ series: s,
301
+ xaxis: { categories: o },
302
+ theme: { mode: r }
303
+ });
304
+ } catch (i) {
305
+ this.render(e, t, r);
306
+ }
307
+ }
308
+ destroy() {
309
+ var e;
310
+ try {
311
+ (e = this.chart) == null || e.destroy();
312
+ } catch (t) {
313
+ }
314
+ this.chart = null;
315
+ }
316
+ // ── Private ─────────────────────────────────────────────────────────────────
317
+ buildOptions(e, t, r, s) {
318
+ const o = e.type;
319
+ return o === "pie" || o === "donut" ? this.buildPieOptions(e, t, r, s) : o === "bar" || o === "line" || o === "area" ? this.buildCartesianOptions(e, t, r, s) : null;
320
+ }
321
+ buildCartesianOptions(e, t, r, s) {
322
+ var g;
323
+ const { series: o, categories: i, xAxisLabel: a, yAxisLabel: f, seriesColors: h } = this.extractSeriesAndCategories(e, t), v = e.properties || {}, m = ["#4f46e5", "#10b981", "#f59e0b", "#ef4444", "#3b82f6", "#8b5cf6", "#ec4899", "#14b8a6"];
324
+ let p;
325
+ if (h.length > 0 && h.length === o.length)
326
+ p = h;
327
+ else {
328
+ const u = v.colors ? Object.values(v.colors).filter(Boolean) : [];
329
+ p = u.length > 0 ? u : m;
330
+ }
331
+ const n = r === "dark";
332
+ return {
333
+ chart: {
334
+ type: e.type === "area" ? "area" : e.type === "line" ? "line" : "bar",
335
+ toolbar: { show: !1 },
336
+ background: "transparent",
337
+ animations: { enabled: !0, speed: 400 },
338
+ fontFamily: "inherit",
339
+ // Explicit height prevents ApexCharts from guessing the flex-child height
340
+ // at render time (which can read as 0 before the browser settles layout).
341
+ height: s != null ? s : "100%"
342
+ },
343
+ theme: { mode: r },
344
+ series: o,
345
+ xaxis: {
346
+ categories: i,
347
+ title: { text: a },
348
+ labels: {
349
+ rotate: i.length > 8 ? -45 : 0,
350
+ style: { colors: n ? "#94a3b8" : "#6b7280", fontSize: "11px" }
351
+ },
352
+ axisBorder: { color: n ? "#334155" : "#e5e7eb" },
353
+ axisTicks: { color: n ? "#334155" : "#e5e7eb" }
354
+ },
355
+ yaxis: {
356
+ title: { text: f },
357
+ labels: { style: { colors: n ? "#94a3b8" : "#6b7280", fontSize: "11px" } }
358
+ },
359
+ colors: p,
360
+ legend: {
361
+ show: o.length > 1 || ((g = v.showLegend) != null ? g : !1),
362
+ position: "bottom",
363
+ labels: { colors: n ? "#94a3b8" : "#6b7280" }
364
+ },
365
+ grid: {
366
+ borderColor: n ? "#1e293b" : "#f3f4f6",
367
+ strokeDashArray: 4,
368
+ padding: {
369
+ bottom: i.length > 8 ? 60 : 10
370
+ }
371
+ },
372
+ tooltip: { theme: r },
373
+ stroke: {
374
+ curve: "smooth",
375
+ width: e.type === "bar" ? 0 : 2
376
+ },
377
+ fill: e.type === "area" ? { type: "gradient", gradient: { shadeIntensity: 0.5, opacityFrom: 0.4, opacityTo: 0 } } : {},
378
+ dataLabels: { enabled: !1 },
379
+ plotOptions: {
380
+ bar: {
381
+ borderRadius: 4,
382
+ distributed: o.length === 1
383
+ }
384
+ }
385
+ };
386
+ }
387
+ buildPieOptions(e, t, r, s) {
388
+ const o = e.properties || {}, i = t.length > 0 ? Object.keys(t[0]) : [], a = i[0] || "label", f = i[1] || "value", h = t.map((u) => {
389
+ var b;
390
+ return String((b = u[a]) != null ? b : "");
391
+ }), v = t.map((u) => Number(u[f]) || 0), m = r === "dark", p = ["#4f46e5", "#10b981", "#f59e0b", "#ef4444", "#3b82f6", "#8b5cf6", "#ec4899", "#14b8a6"], n = o.colors ? Object.values(o.colors).filter(Boolean) : [], g = n.length > 0 ? n : p;
392
+ return {
393
+ chart: {
394
+ type: e.type,
395
+ toolbar: { show: !1 },
396
+ background: "transparent",
397
+ fontFamily: "inherit",
398
+ height: s != null ? s : "100%"
399
+ },
400
+ theme: { mode: r },
401
+ series: v,
402
+ labels: h,
403
+ colors: g,
404
+ legend: {
405
+ position: "bottom",
406
+ labels: { colors: m ? "#94a3b8" : "#6b7280" }
407
+ },
408
+ tooltip: { theme: r },
409
+ dataLabels: { enabled: h.length <= 8 },
410
+ plotOptions: {
411
+ pie: {
412
+ donut: { size: e.type === "donut" ? "65%" : "0%" }
413
+ }
414
+ }
415
+ };
416
+ }
417
+ extractSeriesAndCategories(e, t) {
418
+ const r = e.properties || {}, s = t.length > 0 ? Object.keys(t[0]) : [], o = r.xAxis || s[0] || "x";
419
+ let i = [];
420
+ if (r.selectedYAxisColumn)
421
+ try {
422
+ const n = typeof r.selectedYAxisColumn == "string" ? JSON.parse(r.selectedYAxisColumn) : r.selectedYAxisColumn;
423
+ Array.isArray(n) && (i = n.map(
424
+ (g) => typeof g == "string" ? { column: g } : g
425
+ ));
426
+ } catch (n) {
427
+ }
428
+ if (!i.length && r.yAxis) {
429
+ const n = r.yAxis;
430
+ i = (Array.isArray(n) ? n : [n]).map((u) => ({ column: String(u) }));
431
+ }
432
+ !i.length && s.length > 1 && (i = s.slice(1).map((n) => ({ column: n })));
433
+ const a = t.map((n) => {
434
+ var g;
435
+ return String((g = n[o]) != null ? g : "");
436
+ }), f = t.length > 0 ? Object.keys(t[0]) : [], h = i.map((n) => {
437
+ const g = f.includes(n.column) ? n.column : null, u = g ? null : f.find((d) => d.includes(n.column) || n.column.includes(d)), b = !g && !u ? f.find((d) => {
438
+ var c;
439
+ return d !== o && !isNaN(Number((c = t[0]) == null ? void 0 : c[d]));
440
+ }) : null, l = g || u || b || n.column;
441
+ return {
442
+ // Use the column name directly as the series label (matches builder behaviour).
443
+ // col.label often stores the chart title, not the series name — ignore it.
444
+ name: n.column,
445
+ data: t.map((d) => {
446
+ const c = d[l];
447
+ return c != null ? Number(c) : null;
448
+ })
449
+ };
450
+ }), v = i.map((n) => n.color || n.colorHex || "").filter(Boolean), m = r.xAxisLabel || o, p = r.yAxisLabel || (i.length === 1 ? i[0].column : "");
451
+ return { series: h, categories: a, xAxisLabel: m, yAxisLabel: p, seriesColors: v };
452
+ }
453
+ renderEmpty(e) {
454
+ this.container.innerHTML = `
455
+ <div class="ds-chart-card">
456
+ ${e ? `<div class="ds-chart-title">${e}</div>` : ""}
457
+ <div class="ds-chart-empty">No data available</div>
458
+ </div>
459
+ `;
460
+ }
461
+ }
462
+ class se {
463
+ constructor() {
464
+ this.columns = [], this.allData = [], this.currentRows = [], this.totalRows = 0, this.currentPage = 1, this.currentPageSize = 10, this.searchTerm = "", this.isLoading = !1;
465
+ }
466
+ // ── Public API ─────────────────────────────────────────────────────────────
467
+ render(e, t, r, s) {
468
+ var v;
469
+ this.container = e, this.component = t, this.fetchPage = s, this.searchTerm = "", this.currentPage = 1, this.currentPageSize = 10;
470
+ const o = r && !Array.isArray(r) && "rows" in r;
471
+ if (o) {
472
+ const m = r;
473
+ this.currentRows = m.rows, this.allData = [], this.totalRows = m.total, this.currentPage = m.page, this.currentPageSize = m.pageSize;
474
+ } else
475
+ this.allData = r || [], this.currentRows = [], this.totalRows = this.allData.length, this.currentPage = 1;
476
+ if (o && r.rows.length === 0 && r.total === 0 || !o && this.allData.length === 0) {
477
+ e.innerHTML = `
478
+ <div class="ds-table-card">
479
+ <div class="ds-table-header-row">
480
+ <span class="ds-table-dot"></span>
481
+ <div class="ds-table-title">${this.escape(t.title || "")}</div>
482
+ </div>
483
+ <div class="ds-chart-empty">No data available</div>
484
+ </div>
485
+ `;
486
+ return;
487
+ }
488
+ const i = o ? r.rows[0] : this.allData[0], a = i ? Object.keys(i) : [], f = ((v = t.properties) == null ? void 0 : v.columns) || [], h = f.length ? f.filter((m) => a.includes(m)) : [];
489
+ this.columns = h.length ? h : a, this.paint();
490
+ }
491
+ // ── Rendering ──────────────────────────────────────────────────────────────
492
+ paint() {
493
+ const { rows: e, startNum: t } = this.getPageRows(), r = Math.max(1, Math.ceil(this.totalRows / this.currentPageSize)), s = (this.currentPage - 1) * this.currentPageSize + 1, o = this.fetchPage ? Math.min(s - 1 + (this.isLoading ? this.currentPageSize : e.length), this.totalRows) : Math.min(this.currentPage * this.currentPageSize, this.totalRows), i = this.columns.map((p) => `<th>${this.escape(this.formatHeader(p))}</th>`).join(""), a = e.length === 0 ? `<tr><td colspan="${this.columns.length + 1}" style="text-align:center;padding:20px;color:#9ca3af">No results found</td></tr>` : e.map((p, n) => {
494
+ const g = this.columns.map((u) => {
495
+ const b = p[u];
496
+ return `<td title="${this.escape(String(b != null ? b : ""))}">${this.escape(this.formatValue(b))}</td>`;
497
+ }).join("");
498
+ return `<tr><td class="ds-table-sno">${t + n}</td>${g}</tr>`;
499
+ }).join(""), f = this.currentPageSize, h = [10, 25, 50, 100].map((p) => `<option value="${p}"${p === f ? " selected" : ""}>${p}</option>`).join(""), v = this.currentPage <= 1, m = this.currentPage >= r;
500
+ this.container.innerHTML = `
501
+ <div class="ds-table-card">
502
+ <div class="ds-table-header-row">
503
+ <div class="ds-table-title-wrap">
504
+ <span class="ds-table-dot"></span>
505
+ <span class="ds-table-title">${this.escape(this.component.title || "")}</span>
506
+ </div>
507
+ </div>
508
+ <div class="ds-table-scroll">
509
+ <table class="ds-table">
510
+ <thead><tr><th class="ds-table-sno-th">S.No</th>${i}</tr></thead>
511
+ <tbody>${a}</tbody>
512
+ </table>
513
+ </div>
514
+ <div class="ds-table-footer">
515
+ <div class="ds-table-info">
516
+ <span>${s.toLocaleString()}–${o.toLocaleString()} of ${this.totalRows.toLocaleString()}</span>
517
+ <span class="ds-table-rows-label">Rows:</span>
518
+ <select class="ds-table-page-size">${h}</select>
519
+ </div>
520
+ <div class="ds-table-pagination">
521
+ <button class="ds-table-pg-btn" data-action="first" ${v ? "disabled" : ""}>&#171;</button>
522
+ <button class="ds-table-pg-btn" data-action="prev" ${v ? "disabled" : ""}>Previous</button>
523
+ <span class="ds-table-pg-info">Page ${this.currentPage} of ${r}</span>
524
+ <button class="ds-table-pg-btn" data-action="next" ${m ? "disabled" : ""}>Next</button>
525
+ <button class="ds-table-pg-btn" data-action="last" ${m ? "disabled" : ""}>&#187;</button>
526
+ </div>
527
+ </div>
528
+ ${this.isLoading ? '<div class="ds-table-loading-overlay"><div class="ds-embed-spinner"></div></div>' : ""}
529
+ </div>
530
+ `, this.attachEvents();
531
+ }
532
+ attachEvents() {
533
+ const e = this.container.querySelector(".ds-table-search");
534
+ e == null || e.addEventListener("input", () => {
535
+ this.searchTerm = e.value, this.currentPage = 1, this.fetchPage || (this.totalRows = this.filteredRows().length, this.paint());
536
+ });
537
+ const t = this.container.querySelector(".ds-table-page-size");
538
+ t == null || t.addEventListener("change", () => {
539
+ this.currentPageSize = parseInt(t.value, 10), this.currentPage = 1, this.fetchPage ? this.loadPage(1) : (this.totalRows = this.filteredRows().length, this.paint());
540
+ }), this.container.querySelectorAll("[data-action]").forEach((r) => {
541
+ r.addEventListener("click", () => {
542
+ if (r.disabled) return;
543
+ const s = Math.max(1, Math.ceil(this.totalRows / this.currentPageSize)), o = r.dataset.action;
544
+ let i = this.currentPage;
545
+ o === "first" ? i = 1 : o === "prev" ? i = Math.max(1, this.currentPage - 1) : o === "next" ? i = Math.min(s, this.currentPage + 1) : o === "last" && (i = s), (i !== this.currentPage || o === "first" || o === "last") && (this.currentPage = i, this.fetchPage ? this.loadPage(i) : this.paint());
546
+ });
547
+ });
548
+ }
549
+ // ── Server-side page load ──────────────────────────────────────────────────
550
+ async loadPage(e) {
551
+ if (this.fetchPage) {
552
+ this.isLoading = !0, this.currentPage = e, this.paint();
553
+ try {
554
+ const t = await this.fetchPage(e, this.currentPageSize);
555
+ this.currentRows = t.rows || [], this.totalRows = Number(t.total) || this.currentRows.length, this.currentPage = Number(t.page) || e;
556
+ } catch (t) {
557
+ console.error("[Deepspot SDK] TableRenderer: page fetch failed", t);
558
+ } finally {
559
+ this.isLoading = !1, this.paint();
560
+ }
561
+ }
562
+ }
563
+ // ── Data helpers ───────────────────────────────────────────────────────────
564
+ getPageRows() {
565
+ const e = (this.currentPage - 1) * this.currentPageSize + 1;
566
+ if (this.fetchPage)
567
+ return { rows: this.currentRows, startNum: e };
568
+ const t = this.filteredRows(), r = (this.currentPage - 1) * this.currentPageSize;
569
+ return { rows: t.slice(r, r + this.currentPageSize), startNum: e };
570
+ }
571
+ filteredRows() {
572
+ if (!this.searchTerm) return this.allData;
573
+ const e = this.searchTerm.toLowerCase();
574
+ return this.allData.filter(
575
+ (t) => this.columns.some((r) => {
576
+ var s;
577
+ return String((s = t[r]) != null ? s : "").toLowerCase().includes(e);
578
+ })
579
+ );
580
+ }
581
+ // ── Formatters ─────────────────────────────────────────────────────────────
582
+ formatHeader(e) {
583
+ return e.replace(/_/g, " ").replace(/\b\w/g, (t) => t.toUpperCase());
584
+ }
585
+ formatValue(e) {
586
+ return e == null ? "—" : typeof e == "number" ? e % 1 === 0 ? e.toLocaleString() : e.toFixed(2) : String(e);
587
+ }
588
+ escape(e) {
589
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
590
+ }
591
+ }
592
+ class oe {
593
+ render(e, t, r) {
594
+ var g;
595
+ const s = t.properties || {};
596
+ let o = null;
597
+ if (r && r.length > 0) {
598
+ const u = r[0], b = s.metric || Object.keys(u)[0], l = u[b];
599
+ if (l !== void 0)
600
+ o = l;
601
+ else {
602
+ const d = Object.keys(u).find(
603
+ (c) => c.includes(b) || b.includes(c)
604
+ );
605
+ o = u[d != null ? d : Object.keys(u)[0]];
606
+ }
607
+ }
608
+ let i;
609
+ if (o == null)
610
+ try {
611
+ const u = (g = s == null ? void 0 : s.data) == null ? void 0 : g.dataContent, b = u ? typeof u == "string" ? JSON.parse(u) : u : null;
612
+ i = b != null && b.value ? String(b.value) : "—";
613
+ } catch (u) {
614
+ i = "—";
615
+ }
616
+ else
617
+ i = this.formatValue(o, s);
618
+ const a = s.styleConfig || {}, f = a.backgroundColor || s.backgroundColor || "", h = a.color || a.textColor || s.textColor || "", v = t.title || "", m = f ? `background:${f};border:none;` : "", p = h ? `color:${h};` : "", n = h ? `color:${h};opacity:0.7;` : "";
619
+ e.innerHTML = `
620
+ <div class="ds-card"${m ? ` style="${m}"` : ""}>
621
+ <div class="ds-card-label"${n ? ` style="${n}"` : ""}>${v}</div>
622
+ <div class="ds-card-value"${p ? ` style="${p}"` : ""}>${i}</div>
623
+ </div>
624
+ `;
625
+ }
626
+ formatValue(e, t) {
627
+ if (e == null) return "—";
628
+ const r = Number(e);
629
+ if (isNaN(r)) return String(e);
630
+ const s = (t == null ? void 0 : t.prefix) || (t == null ? void 0 : t.currencySymbol) || "", o = (t == null ? void 0 : t.suffix) || "", i = r % 1 === 0 ? r.toLocaleString() : parseFloat(r.toFixed(2)).toLocaleString();
631
+ return `${s}${i}${o}`;
632
+ }
633
+ }
634
+ const ne = [
635
+ { value: "=", label: "Equals (=)" },
636
+ { value: "!=", label: "Not Equals (!=)" },
637
+ { value: "LIKE", label: "Contains" },
638
+ { value: "NOT LIKE", label: "Not Contains" },
639
+ { value: "STARTS_WITH", label: "Starts With" },
640
+ { value: "IN", label: "In (Multiple)" },
641
+ { value: "NOT IN", label: "Not In" },
642
+ { value: ">", label: "Greater Than (>)" },
643
+ { value: ">=", label: "Greater or Equal (>=)" },
644
+ { value: "<", label: "Less Than (<)" },
645
+ { value: "<=", label: "Less or Equal (<=)" },
646
+ { value: "IS NULL", label: "Is Empty" },
647
+ { value: "IS NOT NULL", label: "Is Not Empty" }
648
+ ];
649
+ class le {
650
+ constructor(e, t) {
651
+ this.filters = [], this.state = /* @__PURE__ */ new Map(), this.activePopoverId = null, this.container = e, this.onFilterChange = t;
652
+ }
653
+ // ── Public API ─────────────────────────────────────────────────────────────
654
+ render(e, t = {}) {
655
+ var r, s, o;
656
+ if (!e || e.length === 0) {
657
+ this.container.style.display = "none";
658
+ return;
659
+ }
660
+ this.container.style.display = "", this.filters = e, this.state.clear();
661
+ for (const i of e) {
662
+ const a = i.applyToField || i.filterId, f = (r = t[i.filterId]) != null ? r : t[a], h = f ? Array.isArray(f) ? f.map(String) : [String(f)] : [];
663
+ this.state.set(i.filterId, {
664
+ operator: i.filterOperator || "=",
665
+ tempValues: [...h],
666
+ activeValues: [...h],
667
+ options: (o = (s = i.options) == null ? void 0 : s.map((v) => v)) != null ? o : [],
668
+ loading: !1,
669
+ search: ""
670
+ });
671
+ }
672
+ this.paint();
673
+ }
674
+ updateOptions(e, t) {
675
+ const r = this.state.get(e);
676
+ r && (r.loading = !1, r.options = t, this.refreshPopoverOpts(e));
677
+ }
678
+ setLoading(e, t) {
679
+ const r = this.state.get(e);
680
+ r && (r.loading = t, this.refreshPopoverOpts(e));
681
+ }
682
+ updateValues(e) {
683
+ for (const [t, r] of Object.entries(e)) {
684
+ const s = this.filters.find(
685
+ (a) => (a.applyToField || a.filterId) === t || a.filterId === t
686
+ );
687
+ if (!s) continue;
688
+ const o = this.state.get(s.filterId);
689
+ if (!o) continue;
690
+ const i = r ? Array.isArray(r) ? r.map(String) : [String(r)] : [];
691
+ o.activeValues = i, o.tempValues = [...i];
692
+ }
693
+ this.repaintBadges();
694
+ }
695
+ // ── Paint badge bar ─────────────────────────────────────────────────────────
696
+ paint() {
697
+ this.container.innerHTML = `
698
+ <div class="ds-fp-v3">
699
+ <div class="ds-fp-v3-bar" id="ds-fp-v3-bar">
700
+ <span class="ds-fp-v3-label">
701
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
702
+ stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
703
+ <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
704
+ </svg>
705
+ Filters:
706
+ </span>
707
+ <div class="ds-fp-v3-badges" id="ds-fp-v3-badges">
708
+ ${this.filters.map((e) => this.renderBadge(e)).join("")}
709
+ </div>
710
+ </div>
711
+ <!-- Popovers rendered per filter -->
712
+ ${this.filters.map((e) => this.renderPopoverHtml(e)).join("")}
713
+ </div>`, this.bindAll(), this.closePopoverOnOutsideClick();
714
+ }
715
+ renderBadge(e) {
716
+ return `
717
+ <button type="button"
718
+ class="ds-fp-v3-badge${this.state.get(e.filterId).activeValues.length > 0 ? " ds-fp-v3-badge-active" : ""}"
719
+ data-filter-id="${this.escAttr(e.filterId)}">
720
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
721
+ stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
722
+ <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
723
+ </svg>
724
+ ${this.escText(e.label)}
725
+ </button>`;
726
+ }
727
+ renderPopoverHtml(e) {
728
+ const t = this.state.get(e.filterId);
729
+ return `
730
+ <div class="ds-fp-v3-pop" id="ds-fp-pop-${this.escAttr(e.filterId)}" style="display:none">
731
+ <div class="ds-fp-v3-pop-hd">
732
+ <span class="ds-fp-v3-pop-title">
733
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
734
+ stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
735
+ <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
736
+ </svg>
737
+ Filter
738
+ </span>
739
+ <div class="ds-fp-v3-pop-actions">
740
+ <button type="button" class="ds-fp-v3-pop-icon-btn ds-fp-v3-pop-close-btn"
741
+ data-filter-id="${this.escAttr(e.filterId)}" title="Close">
742
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
743
+ stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
744
+ <line x1="18" y1="6" x2="6" y2="18"/>
745
+ <line x1="6" y1="6" x2="18" y2="18"/>
746
+ </svg>
747
+ </button>
748
+ </div>
749
+ </div>
750
+ <div class="ds-fp-v3-pop-body">
751
+ <div class="ds-fp-v3-field">
752
+ <label class="ds-fp-v3-field-lbl">CONDITION</label>
753
+ <select class="ds-fp-v3-condition" data-filter-id="${this.escAttr(e.filterId)}">
754
+ ${ne.map(
755
+ (r) => `<option value="${this.escAttr(r.value)}"${r.value === t.operator ? " selected" : ""}>${r.label}</option>`
756
+ ).join("")}
757
+ </select>
758
+ </div>
759
+ <div class="ds-fp-v3-field">
760
+ <label class="ds-fp-v3-field-lbl">VALUES</label>
761
+ ${this.renderValuesControl(e, t)}
762
+ </div>
763
+ </div>
764
+ <div class="ds-fp-v3-pop-footer">
765
+ <button type="button" class="ds-fp-v3-clear-btn"
766
+ data-filter-id="${this.escAttr(e.filterId)}">Clear</button>
767
+ <button type="button" class="ds-fp-v3-apply-btn"
768
+ data-filter-id="${this.escAttr(e.filterId)}">Apply Filter</button>
769
+ </div>
770
+ </div>`;
771
+ }
772
+ renderValuesControl(e, t) {
773
+ var r, s, o;
774
+ return e.type === "date-range" ? `
775
+ <div class="ds-fp-v3-date-range">
776
+ <input type="date" class="ds-fp-v3-date" data-part="from"
777
+ data-filter-id="${this.escAttr(e.filterId)}"
778
+ value="${this.escAttr((r = t.tempValues[0]) != null ? r : "")}" placeholder="From"/>
779
+ <span class="ds-fp-v3-date-sep">–</span>
780
+ <input type="date" class="ds-fp-v3-date" data-part="to"
781
+ data-filter-id="${this.escAttr(e.filterId)}"
782
+ value="${this.escAttr((s = t.tempValues[1]) != null ? s : "")}" placeholder="To"/>
783
+ </div>` : e.type === "text-input" || e.type === "number-input" ? `<input type="${e.type === "number-input" ? "number" : "text"}"
784
+ class="ds-fp-v3-text-input"
785
+ data-filter-id="${this.escAttr(e.filterId)}"
786
+ value="${this.escAttr((o = t.tempValues[0]) != null ? o : "")}"
787
+ placeholder="Enter value…"/>` : this.renderMultiSelectControl(e, t);
788
+ }
789
+ renderMultiSelectControl(e, t) {
790
+ const r = t.tempValues.length === 0 ? "" : t.tempValues.length === 1 ? this.optLabel(e.filterId, t.tempValues[0]) : `${t.tempValues.length} selected`;
791
+ return `
792
+ <div class="ds-fp-v3-ms-wrap" data-filter-id="${this.escAttr(e.filterId)}">
793
+ <div class="ds-fp-v3-ms-trigger" data-filter-id="${this.escAttr(e.filterId)}">
794
+ <span class="ds-fp-v3-ms-display">${r || "Type to search..."}</span>
795
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
796
+ stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
797
+ <polyline points="6 9 12 15 18 9"/>
798
+ </svg>
799
+ </div>
800
+ <div class="ds-fp-v3-ms-drop" id="ds-fp-drop-${this.escAttr(e.filterId)}" style="display:none">
801
+ <input type="text" class="ds-fp-v3-ms-search"
802
+ data-filter-id="${this.escAttr(e.filterId)}"
803
+ placeholder="Search ${this.escAttr(e.label)}…"
804
+ value="${this.escAttr(t.search)}"/>
805
+ <div class="ds-fp-v3-ms-opts" id="ds-fp-opts-${this.escAttr(e.filterId)}">
806
+ ${this.renderOpts(e.filterId, t)}
807
+ </div>
808
+ </div>
809
+ ${t.tempValues.length > 0 ? `
810
+ <div class="ds-fp-v3-chips" id="ds-fp-chips-${this.escAttr(e.filterId)}">
811
+ ${t.tempValues.map((s) => `
812
+ <span class="ds-fp-v3-chip">
813
+ ${this.escText(this.optLabel(e.filterId, s))}
814
+ <button type="button" class="ds-fp-v3-chip-x"
815
+ data-val="${this.escAttr(s)}"
816
+ data-filter-id="${this.escAttr(e.filterId)}">✕</button>
817
+ </span>`).join("")}
818
+ </div>` : `<div class="ds-fp-v3-chips" id="ds-fp-chips-${this.escAttr(e.filterId)}"></div>`}
819
+ </div>`;
820
+ }
821
+ renderOpts(e, t) {
822
+ if (t.loading) return '<div class="ds-fp-v3-opts-msg"><span class="ds-dd-spinner"></span> Loading…</div>';
823
+ const r = t.search.toLowerCase(), s = t.options.filter(
824
+ (o) => (typeof o == "string" ? o : o.label).toLowerCase().includes(r)
825
+ );
826
+ return t.options.length ? s.length ? s.map((o) => {
827
+ const i = typeof o == "string" ? o : o.value, a = typeof o == "string" ? o : o.label, f = t.tempValues.includes(i);
828
+ return `
829
+ <label class="ds-fp-v3-opt${f ? " ds-fp-v3-opt-on" : ""}" data-val="${this.escAttr(i)}"
830
+ data-filter-id="${this.escAttr(e)}">
831
+ <span class="ds-fp-v3-cb${f ? " ds-fp-v3-cb-on" : ""}">
832
+ ${f ? `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"
833
+ stroke-linecap="round" stroke-linejoin="round" width="10" height="10">
834
+ <polyline points="20 6 9 17 4 12"/>
835
+ </svg>` : ""}
836
+ </span>
837
+ <span>${this.escText(a)}</span>
838
+ </label>`;
839
+ }).join("") : '<div class="ds-fp-v3-opts-msg">No matches</div>' : '<div class="ds-fp-v3-opts-msg">No options yet</div>';
840
+ }
841
+ // ── Event binding ──────────────────────────────────────────────────────────
842
+ bindAll() {
843
+ var e;
844
+ this.container.querySelectorAll(".ds-fp-v3-badge").forEach((t) => {
845
+ t.addEventListener("click", (r) => {
846
+ r.stopPropagation();
847
+ const s = t.dataset.filterId;
848
+ this.togglePopover(s, t);
849
+ });
850
+ }), (e = this.container.querySelector("#ds-fp-v3-add")) == null || e.addEventListener("click", (t) => {
851
+ t.stopPropagation();
852
+ const r = this.filters.find((o) => this.state.get(o.filterId).activeValues.length === 0);
853
+ if (!r) return;
854
+ const s = this.container.querySelector(
855
+ `.ds-fp-v3-badge[data-filter-id="${r.filterId}"]`
856
+ );
857
+ s && this.togglePopover(r.filterId, s);
858
+ }), this.filters.forEach((t) => this.bindPopover(t));
859
+ }
860
+ bindPopover(e) {
861
+ var o, i, a, f, h, v, m;
862
+ const t = this.container.querySelector(`#ds-fp-pop-${e.filterId}`);
863
+ if (!t) return;
864
+ const r = this.state.get(e.filterId);
865
+ (o = t.querySelector(`.ds-fp-v3-condition[data-filter-id="${e.filterId}"]`)) == null || o.addEventListener("change", (p) => {
866
+ r.operator = p.target.value;
867
+ }), (i = t.querySelector(`.ds-fp-v3-text-input[data-filter-id="${e.filterId}"]`)) == null || i.addEventListener("input", (p) => {
868
+ const n = p.target.value;
869
+ r.tempValues = n ? [n] : [];
870
+ }), t.querySelectorAll(`.ds-fp-v3-date[data-filter-id="${e.filterId}"]`).forEach((p) => {
871
+ p.addEventListener("change", () => {
872
+ var u, b, l, d;
873
+ const n = (b = (u = t.querySelector(`[data-part="from"][data-filter-id="${e.filterId}"]`)) == null ? void 0 : u.value) != null ? b : "", g = (d = (l = t.querySelector(`[data-part="to"][data-filter-id="${e.filterId}"]`)) == null ? void 0 : l.value) != null ? d : "";
874
+ r.tempValues = [n, g];
875
+ });
876
+ }), (a = t.querySelector(`.ds-fp-v3-ms-trigger[data-filter-id="${e.filterId}"]`)) == null || a.addEventListener("click", (p) => {
877
+ p.stopPropagation();
878
+ const n = t.querySelector(`#ds-fp-drop-${e.filterId}`);
879
+ if (n) {
880
+ const g = n.style.display !== "none";
881
+ if (n.style.display = g ? "none" : "", !g) {
882
+ const u = n.querySelector(".ds-fp-v3-ms-search");
883
+ u == null || u.focus();
884
+ }
885
+ }
886
+ });
887
+ const s = t.querySelector(`.ds-fp-v3-ms-search[data-filter-id="${e.filterId}"]`);
888
+ s && s.addEventListener("input", () => {
889
+ r.search = s.value, this.repaintOpts(e.filterId);
890
+ }), this.bindOpts(e.filterId, t), this.bindChips(e.filterId, t), (f = t.querySelector(`.ds-fp-v3-pop-clear-btn[data-filter-id="${e.filterId}"]`)) == null || f.addEventListener("click", () => this.clearFilter(e.filterId)), (h = t.querySelector(`.ds-fp-v3-pop-close-btn[data-filter-id="${e.filterId}"]`)) == null || h.addEventListener("click", () => this.closePopover(e.filterId)), (v = t.querySelector(`.ds-fp-v3-clear-btn[data-filter-id="${e.filterId}"]`)) == null || v.addEventListener("click", () => this.clearFilter(e.filterId)), (m = t.querySelector(`.ds-fp-v3-apply-btn[data-filter-id="${e.filterId}"]`)) == null || m.addEventListener("click", () => this.applyFilter(e.filterId));
891
+ }
892
+ bindOpts(e, t) {
893
+ const r = this.state.get(e);
894
+ t.querySelectorAll(`.ds-fp-v3-opt[data-filter-id="${e}"]`).forEach((s) => {
895
+ s.addEventListener("click", (o) => {
896
+ var a;
897
+ o.stopPropagation();
898
+ const i = (a = s.dataset.val) != null ? a : "";
899
+ r.tempValues.includes(i) ? r.tempValues = r.tempValues.filter((f) => f !== i) : r.tempValues = [...r.tempValues, i], this.repaintOpts(e), this.repaintChips(e);
900
+ });
901
+ });
902
+ }
903
+ bindChips(e, t) {
904
+ const r = this.state.get(e);
905
+ t.querySelectorAll(`.ds-fp-v3-chip-x[data-filter-id="${e}"]`).forEach((s) => {
906
+ s.addEventListener("click", (o) => {
907
+ o.stopPropagation(), r.tempValues = r.tempValues.filter((i) => {
908
+ var a;
909
+ return i !== ((a = s.dataset.val) != null ? a : "");
910
+ }), this.repaintOpts(e), this.repaintChips(e);
911
+ });
912
+ });
913
+ }
914
+ // ── Popover show/hide ──────────────────────────────────────────────────────
915
+ togglePopover(e, t) {
916
+ if (this.activePopoverId === e) {
917
+ this.closePopover(e);
918
+ return;
919
+ }
920
+ this.activePopoverId && this.closePopover(this.activePopoverId);
921
+ const r = this.container.querySelector(`#ds-fp-pop-${e}`);
922
+ if (!r) return;
923
+ const s = this.container.getBoundingClientRect(), o = t.getBoundingClientRect();
924
+ r.style.top = `${o.bottom - s.top + 4}px`, r.style.left = `${o.left - s.left}px`, r.style.display = "", this.activePopoverId = e;
925
+ const i = this.state.get(e);
926
+ i.tempValues = [...i.activeValues], i.search = "", this.repaintOpts(e), this.repaintChips(e);
927
+ const a = r.querySelector(`#ds-fp-drop-${e}`);
928
+ a && (a.style.display = "none");
929
+ }
930
+ closePopover(e) {
931
+ const t = this.container.querySelector(`#ds-fp-pop-${e}`);
932
+ t && (t.style.display = "none"), this.activePopoverId === e && (this.activePopoverId = null);
933
+ }
934
+ closePopoverOnOutsideClick() {
935
+ document.addEventListener("mousedown", (e) => {
936
+ if (!this.activePopoverId) return;
937
+ const t = this.container.querySelector(`#ds-fp-pop-${this.activePopoverId}`), r = this.container.querySelector("#ds-fp-v3-bar");
938
+ t && !t.contains(e.target) && r && !r.contains(e.target) && this.closePopover(this.activePopoverId);
939
+ }, { capture: !0 });
940
+ }
941
+ // ── Apply / clear ──────────────────────────────────────────────────────────
942
+ applyFilter(e) {
943
+ var o;
944
+ const t = this.state.get(e), r = this.filters.find((i) => i.filterId === e);
945
+ t.activeValues = [...t.tempValues];
946
+ let s;
947
+ r.type === "date-range" ? s = t.tempValues.length >= 2 && (t.tempValues[0] || t.tempValues[1]) ? { from: t.tempValues[0], to: t.tempValues[1] } : "" : r.type === "multi-select" || t.tempValues.length > 1 ? s = t.tempValues.length > 0 ? [...t.tempValues] : "" : s = (o = t.tempValues[0]) != null ? o : "", this.repaintBadge(e), this.closePopover(e), this.onFilterChange(e, s, t.operator);
948
+ }
949
+ clearFilter(e) {
950
+ const t = this.state.get(e);
951
+ t.tempValues = [], t.activeValues = [], this.repaintBadge(e), this.repaintOpts(e), this.repaintChips(e), this.closePopover(e), this.onFilterChange(e, "", void 0);
952
+ }
953
+ // ── Repaint helpers ────────────────────────────────────────────────────────
954
+ repaintBadge(e) {
955
+ const t = this.state.get(e), r = this.container.querySelector(`.ds-fp-v3-badge[data-filter-id="${e}"]`);
956
+ r && (t.activeValues.length > 0 ? r.classList.add("ds-fp-v3-badge-active") : r.classList.remove("ds-fp-v3-badge-active"));
957
+ }
958
+ repaintBadges() {
959
+ this.filters.forEach((e) => this.repaintBadge(e.filterId));
960
+ }
961
+ repaintOpts(e) {
962
+ const t = this.state.get(e);
963
+ if (!t) return;
964
+ const r = this.container.querySelector(`#ds-fp-opts-${e}`);
965
+ if (!r) return;
966
+ r.innerHTML = this.renderOpts(e, t);
967
+ const s = this.container.querySelector(`#ds-fp-pop-${e}`);
968
+ s && this.bindOpts(e, s);
969
+ }
970
+ refreshPopoverOpts(e) {
971
+ this.repaintOpts(e);
972
+ const t = this.state.get(e);
973
+ if (!t) return;
974
+ const r = this.container.querySelector(
975
+ `.ds-fp-v3-ms-trigger[data-filter-id="${e}"] .ds-fp-v3-ms-display`
976
+ );
977
+ r && (r.textContent = t.tempValues.length === 0 ? "Type to search..." : t.tempValues.length === 1 ? this.optLabel(e, t.tempValues[0]) : `${t.tempValues.length} selected`);
978
+ }
979
+ repaintChips(e) {
980
+ const t = this.state.get(e);
981
+ if (!t) return;
982
+ const r = this.container.querySelector(`#ds-fp-chips-${e}`);
983
+ if (!r) return;
984
+ if (t.tempValues.length === 0) {
985
+ r.innerHTML = "";
986
+ const i = this.container.querySelector(
987
+ `.ds-fp-v3-ms-trigger[data-filter-id="${e}"] .ds-fp-v3-ms-display`
988
+ );
989
+ i && (i.textContent = "Type to search...");
990
+ return;
991
+ }
992
+ const s = this.filters.find((i) => i.filterId === e);
993
+ r.innerHTML = t.tempValues.map((i) => `
994
+ <span class="ds-fp-v3-chip">
995
+ ${this.escText(this.optLabel(s.filterId, i))}
996
+ <button type="button" class="ds-fp-v3-chip-x"
997
+ data-val="${this.escAttr(i)}"
998
+ data-filter-id="${this.escAttr(e)}">✕</button>
999
+ </span>`).join(""), this.bindChips(e, r);
1000
+ const o = this.container.querySelector(
1001
+ `.ds-fp-v3-ms-trigger[data-filter-id="${e}"] .ds-fp-v3-ms-display`
1002
+ );
1003
+ o && (o.textContent = t.tempValues.length === 1 ? this.optLabel(e, t.tempValues[0]) : `${t.tempValues.length} selected`);
1004
+ }
1005
+ // ── Emit reset-all signal ──────────────────────────────────────────────────
1006
+ resetAll() {
1007
+ this.state.forEach((e) => {
1008
+ e.tempValues = [], e.activeValues = [];
1009
+ }), this.repaintBadges(), this.activePopoverId && this.closePopover(this.activePopoverId), this.onFilterChange("__reset_all__", null);
1010
+ }
1011
+ // ── Utils ──────────────────────────────────────────────────────────────────
1012
+ optLabel(e, t) {
1013
+ const r = this.state.get(e);
1014
+ if (!r) return t;
1015
+ const s = r.options.find((o) => (typeof o == "string" ? o : o.value) === t);
1016
+ return s ? typeof s == "string" ? s : s.label : t;
1017
+ }
1018
+ escAttr(e) {
1019
+ return String(e != null ? e : "").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1020
+ }
1021
+ escText(e) {
1022
+ return String(e != null ? e : "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1023
+ }
1024
+ }
1025
+ const z = class z {
1026
+ constructor(e, t) {
1027
+ this.activePageId = "", this.activeTabId = "", this.activeFilters = {}, this.activeFilterOperators = {}, this.activeLocalFilters = {}, this.chartRenderers = /* @__PURE__ */ new Map(), this.tableRenderer = new se(), this.cardRenderer = new oe(), this.isLoadingTab = !1, this.tooltipEl = null, this.root = e, this.opts = t, this.activeFilters = { ...t.initialFilters };
1028
+ }
1029
+ // ── Initial full render ───────────────────────────────────────────────────
1030
+ render(e) {
1031
+ var t, r;
1032
+ 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);
1033
+ }
1034
+ // ── Called after lazy tab fetch completes ─────────────────────────────────
1035
+ update(e) {
1036
+ this.renderData = e, this.activePageId = e.activePage, this.activeTabId = e.activeTab, this.isLoadingTab = !1, this.syncNavHighlight(), this.updateFilterBar(), this.renderGrid();
1037
+ }
1038
+ goToPage(e) {
1039
+ const t = this.getTabsForPage(e)[0];
1040
+ t && this.triggerTabSwitch(e, t.id);
1041
+ }
1042
+ goToTab(e, t) {
1043
+ this.triggerTabSwitch(e, t);
1044
+ }
1045
+ destroy() {
1046
+ this.chartRenderers.forEach((e) => e.destroy()), this.chartRenderers.clear(), this.root.innerHTML = "";
1047
+ }
1048
+ exportPDF() {
1049
+ var p, n;
1050
+ const e = this.root.querySelector("#ds-grid");
1051
+ if (!e) return;
1052
+ const t = this.root.style.height, r = this.root.style.overflow;
1053
+ this.root.style.height = "auto", this.root.style.overflow = "visible";
1054
+ const s = this.root.querySelector("#ds-canvas"), o = (p = s == null ? void 0 : s.style.height) != null ? p : "", i = (n = s == null ? void 0 : s.style.overflow) != null ? n : "";
1055
+ s && (s.style.height = "auto", s.style.overflow = "visible");
1056
+ const a = Array.from(e.querySelectorAll(".ds-table-scroll")), f = a.map((g) => ({
1057
+ el: g,
1058
+ overflow: g.style.overflow,
1059
+ maxHeight: g.style.maxHeight,
1060
+ height: g.style.height
1061
+ }));
1062
+ a.forEach((g) => {
1063
+ g.style.overflow = "visible", g.style.maxHeight = "none", g.style.height = "auto";
1064
+ });
1065
+ const h = e.offsetWidth, v = parseInt(e.style.height, 10) || e.offsetHeight, m = e.getBoundingClientRect().top + window.scrollY;
1066
+ import("./html2canvas.esm-CzwMv54K.js").then(
1067
+ ({ default: g }) => g(e, {
1068
+ scale: 2,
1069
+ useCORS: !0,
1070
+ allowTaint: !0,
1071
+ logging: !1,
1072
+ width: h,
1073
+ height: v,
1074
+ windowWidth: h,
1075
+ windowHeight: v,
1076
+ scrollX: 0,
1077
+ scrollY: -m
1078
+ }).then((u) => {
1079
+ this.root.style.height = t, this.root.style.overflow = r, s && (s.style.height = o, s.style.overflow = i), f.forEach(({ el: b, overflow: l, maxHeight: d, height: c }) => {
1080
+ b.style.overflow = l, b.style.maxHeight = d, b.style.height = c;
1081
+ }), import("./jspdf.es.min-Ge0fRUwj.js").then((b) => b.j).then(({ jsPDF: b }) => {
1082
+ const l = u.width / 2, d = u.height / 2, c = new b({
1083
+ orientation: l >= d ? "landscape" : "portrait",
1084
+ unit: "px",
1085
+ format: [l, d]
1086
+ });
1087
+ c.addImage(u.toDataURL("image/png"), "PNG", 0, 0, l, d), c.save(`${this.renderData.dashboard.name || "dashboard"}.pdf`);
1088
+ });
1089
+ })
1090
+ );
1091
+ }
1092
+ exportCSV() {
1093
+ this.renderData.components.forEach((e) => {
1094
+ const t = this.renderData.data[e.id];
1095
+ if (!(t != null && t.length) || !["table", "bar", "line", "area"].includes(e.type)) return;
1096
+ const r = Object.keys(t[0]), s = [r.join(","), ...t.map(
1097
+ (i) => r.map((a) => {
1098
+ var f;
1099
+ return JSON.stringify((f = i[a]) != null ? f : "");
1100
+ }).join(",")
1101
+ )].join(`
1102
+ `);
1103
+ Object.assign(document.createElement("a"), {
1104
+ href: URL.createObjectURL(new Blob([s], { type: "text/csv" })),
1105
+ download: `${e.title || e.id}.csv`
1106
+ }).click();
1107
+ });
1108
+ }
1109
+ /** Returns a snapshot of the currently active filter values (used by DeepspotSDK onTabSwitch). */
1110
+ getActiveFilters() {
1111
+ return { ...this.activeFilters };
1112
+ }
1113
+ /** Returns a snapshot of the active filter operators (=, LIKE, IN, etc.) keyed by applyToField. */
1114
+ getActiveFilterOperators() {
1115
+ return { ...this.activeFilterOperators };
1116
+ }
1117
+ /** Returns local (component-scoped) filter values keyed by filterId. */
1118
+ getActiveLocalFilters() {
1119
+ return { ...this.activeLocalFilters };
1120
+ }
1121
+ updateFilterValues(e) {
1122
+ var t;
1123
+ Object.assign(this.activeFilters, e), (t = this.filterRenderer) == null || t.updateValues(e);
1124
+ }
1125
+ // ── Shell ─────────────────────────────────────────────────────────────────
1126
+ buildShell() {
1127
+ const e = this.opts.embedLevel, t = this.renderData.dashboard.pages, r = e === "dashboard" && t.length > 1, s = e === "page" || e === "dashboard";
1128
+ this.root.innerHTML = `
1129
+ ${this.opts.hideExport ? "" : '<div class="ds-toolbar" id="ds-toolbar"></div>'}
1130
+ ${r ? '<nav class="ds-page-nav" id="ds-page-nav"></nav>' : ""}
1131
+ ${s ? '<nav class="ds-page-nav" id="ds-tab-nav"></nav>' : ""}
1132
+ <div id="ds-filter-container"></div>
1133
+ <div class="ds-canvas" id="ds-canvas">
1134
+ <div class="ds-grid" id="ds-grid"></div>
1135
+ </div>
1136
+ <div id="ds-tab-loading" style="display:none;position:absolute;inset:0;
1137
+ background:rgba(255,255,255,0.5);align-items:center;justify-content:center;">
1138
+ <div class="ds-embed-spinner"></div>
1139
+ </div>
1140
+ `, this.root.style.position = "relative", this.opts.hideExport || this.buildExportToolbar();
1141
+ const o = this.root.querySelector("#ds-filter-container");
1142
+ this.filterRenderer = new le(o, (i, a, f) => {
1143
+ var b, l, d, c, $, w, k, x, T, y, C;
1144
+ if (i === "__reset_all__") {
1145
+ this.activeFilters = {}, this.activeFilterOperators = {}, this.activeLocalFilters = {}, (l = (b = this.opts).onFilterChange) == null || l.call(b, this.activeFilters), this.opts.onTabSwitch(this.activePageId, this.activeTabId);
1146
+ return;
1147
+ }
1148
+ const h = (d = this.renderData.filters) == null ? void 0 : d.find((I) => I.filterId === i), v = (c = this.renderData.components) == null ? void 0 : c.find(
1149
+ (I) => I.type === "inline-filter" && I.id === i
1150
+ ), m = (h == null ? void 0 : h.applyToField) || (($ = v == null ? void 0 : v.properties) == null ? void 0 : $.applyToField) || i, p = (x = (k = h == null ? void 0 : h.columnMappings) != null ? k : (w = v == null ? void 0 : v.properties) == null ? void 0 : w.columnMappings) != null ? x : {}, g = ((T = h == null ? void 0 : h.targetComponents) != null ? T : []).length > 0 || Object.keys(p).length > 0, u = a === "" || a === null || a === void 0 || Array.isArray(a) && a.length === 0;
1151
+ g ? u ? delete this.activeLocalFilters[i] : this.activeLocalFilters[i] = { value: a, operator: f || "=" } : u ? (delete this.activeFilters[m], delete this.activeFilterOperators[m]) : (this.activeFilters[m] = a, f ? this.activeFilterOperators[m] = f : delete this.activeFilterOperators[m]), (C = (y = this.opts).onFilterChange) == null || C.call(y, this.activeFilters), this.opts.onTabSwitch(this.activePageId, this.activeTabId);
1152
+ });
1153
+ }
1154
+ buildExportToolbar() {
1155
+ var t, r;
1156
+ const e = this.root.querySelector("#ds-toolbar");
1157
+ e && (e.innerHTML = `
1158
+ <button class="ds-toolbar-btn" id="ds-btn-pdf">⬇ PDF</button>
1159
+ <button class="ds-toolbar-btn" id="ds-btn-csv" style="display:none">⬇ CSV</button>
1160
+ `, (t = e.querySelector("#ds-btn-pdf")) == null || t.addEventListener("click", () => this.exportPDF()), (r = e.querySelector("#ds-btn-csv")) == null || r.addEventListener("click", () => this.exportCSV()));
1161
+ }
1162
+ // ── Navigation ────────────────────────────────────────────────────────────
1163
+ renderNavigation() {
1164
+ this.renderPageNav(), this.renderTabNav(this.activePageId);
1165
+ }
1166
+ renderPageNav() {
1167
+ const e = this.root.querySelector("#ds-page-nav");
1168
+ e && (e.innerHTML = this.renderData.dashboard.pages.map((t) => `
1169
+ <button class="ds-page-tab ${t.pageId === this.activePageId ? "ds-active" : ""}"
1170
+ data-page-id="${t.pageId}">${t.title}</button>
1171
+ `).join(""), e.querySelectorAll(".ds-page-tab").forEach((t) => {
1172
+ t.addEventListener("click", () => {
1173
+ var s, o;
1174
+ const r = t.dataset.pageId;
1175
+ r === this.activePageId && !this.isLoadingTab || this.triggerTabSwitch(r, (o = (s = this.getTabsForPage(r)[0]) == null ? void 0 : s.id) != null ? o : "");
1176
+ });
1177
+ }));
1178
+ }
1179
+ renderTabNav(e) {
1180
+ const t = this.root.querySelector("#ds-tab-nav");
1181
+ t && this.buildTabButtons(t, e);
1182
+ }
1183
+ buildTabButtons(e, t) {
1184
+ const r = this.getTabsForPage(t);
1185
+ if (r.length <= 1) {
1186
+ e.style.display = "none";
1187
+ return;
1188
+ }
1189
+ e.style.display = "", e.innerHTML = r.map((s) => `
1190
+ <button class="ds-page-tab ${s.id === this.activeTabId ? "ds-active" : ""}"
1191
+ data-tab-id="${s.id}" data-page-id="${t}">${s.title}</button>
1192
+ `).join(""), e.querySelectorAll(".ds-page-tab").forEach((s) => {
1193
+ s.addEventListener("click", () => {
1194
+ const o = s.dataset.tabId, i = s.dataset.pageId;
1195
+ o === this.activeTabId && i === this.activePageId && !this.isLoadingTab || this.triggerTabSwitch(i, o);
1196
+ });
1197
+ });
1198
+ }
1199
+ triggerTabSwitch(e, t) {
1200
+ this.isLoadingTab || !e || !t || (this.isLoadingTab = !0, this.activePageId = e, this.activeTabId = t, this.syncNavHighlight(), this.showTabSpinner(), this.opts.onTabSwitch(e, t));
1201
+ }
1202
+ syncNavHighlight() {
1203
+ this.root.querySelectorAll("#ds-page-nav .ds-page-tab").forEach((t) => {
1204
+ t.classList.toggle("ds-active", t.dataset.pageId === this.activePageId);
1205
+ });
1206
+ const e = this.root.querySelector("#ds-tab-nav");
1207
+ e && this.buildTabButtons(e, this.activePageId);
1208
+ }
1209
+ // ── Global Filter bar ─────────────────────────────────────────────────────
1210
+ // Merges global filters (from renderData.filters[]) with inline-filter
1211
+ // components (type='inline-filter' in renderData.components[]).
1212
+ // Inline-filter components are NOT rendered in the grid — they appear here.
1213
+ renderFilterBar() {
1214
+ this.opts.hideGlobalFilters || (this.filterRenderer.render(this.globalFilters(), this.mergedFilterValues()), this.fetchFilterOptions());
1215
+ }
1216
+ updateFilterBar() {
1217
+ this.opts.hideGlobalFilters || (this.filterRenderer.render(this.globalFilters(), this.mergedFilterValues()), this.fetchFilterOptions());
1218
+ }
1219
+ /** Combines global activeFilters and local filter values (keyed by filterId)
1220
+ * so FilterRenderer can show correct active state for targeted filters. */
1221
+ mergedFilterValues() {
1222
+ const e = { ...this.activeFilters };
1223
+ for (const [t, { value: r }] of Object.entries(this.activeLocalFilters))
1224
+ e[t] = r;
1225
+ return e;
1226
+ }
1227
+ /**
1228
+ * Returns true when a filter should appear in the global top filter bar.
1229
+ * A filter is global when the matching inline-filter component has
1230
+ * filter_scope/scope !== 'local' and filterLevel !== 'component'.
1231
+ */
1232
+ filterIsGlobal(e) {
1233
+ var s, o;
1234
+ const t = (this.renderData.components || []).find(
1235
+ (i) => i.type === "inline-filter" && i.id === e
1236
+ );
1237
+ if (t) {
1238
+ const i = ((s = t.properties) == null ? void 0 : s.filter_scope) || ((o = t.properties) == null ? void 0 : o.scope);
1239
+ if (i === "local") return !1;
1240
+ if (i === "global") return !0;
1241
+ }
1242
+ const r = (this.renderData.filters || []).find((i) => i.filterId === e);
1243
+ return !(r != null && r.filterLevel) || r.filterLevel === "global";
1244
+ }
1245
+ /** Filters from renderData.filters[] that should appear in the global top panel. */
1246
+ globalFilters() {
1247
+ return (this.renderData.filters || []).filter((e) => this.filterIsGlobal(e.filterId));
1248
+ }
1249
+ /**
1250
+ * Filters that are component-scoped (filter_scope='local').
1251
+ * Source of truth is filters[] — which has applyToField, targetComponents, etc.
1252
+ * The matching inline-filter component in components[] provides the scope signal.
1253
+ */
1254
+ inlineFiltersAsDefinitions() {
1255
+ return this.opts.hideInlineFilters ? [] : (this.renderData.filters || []).filter((e) => {
1256
+ var t, r;
1257
+ return !this.filterIsGlobal(e.filterId) && ((r = (t = e.targetComponents) == null ? void 0 : t.length) != null ? r : 0) > 0;
1258
+ }).map((e) => ({ ...e, filterLevel: "component" }));
1259
+ }
1260
+ async fetchFilterOptions() {
1261
+ if (this.opts.onFetchFilterOptions) {
1262
+ for (const e of this.globalFilters())
1263
+ if (!(e.type !== "dropdown" && e.type !== "multi-select") && !(e.options && e.options.length > 0)) {
1264
+ this.filterRenderer.setLoading(e.filterId, !0);
1265
+ try {
1266
+ const t = await this.opts.onFetchFilterOptions(e.filterId);
1267
+ this.filterRenderer.updateOptions(e.filterId, t);
1268
+ } catch (t) {
1269
+ this.filterRenderer.setLoading(e.filterId, !1);
1270
+ }
1271
+ }
1272
+ }
1273
+ }
1274
+ // ── Grid ──────────────────────────────────────────────────────────────────
1275
+ renderGrid() {
1276
+ var m;
1277
+ this.hideTabSpinner();
1278
+ const e = this.root.querySelector("#ds-grid");
1279
+ if (!e) return;
1280
+ if (!this.opts.hideBackground) {
1281
+ const p = this.root.querySelector("#ds-canvas"), n = this.renderData.dashboard.pages.find(
1282
+ (u) => u.pageId === this.activePageId
1283
+ ), g = (n == null ? void 0 : n.backgroundColor) || "";
1284
+ p && (p.style.background = g), this.root.style.background = g;
1285
+ }
1286
+ this.chartRenderers.forEach((p) => p.destroy()), this.chartRenderers.clear();
1287
+ const t = (this.renderData.components || []).filter(
1288
+ (p) => p.type !== "inline-filter"
1289
+ );
1290
+ if (!t.length) {
1291
+ e.style.height = "200px", e.innerHTML = '<div class="ds-chart-empty" style="padding-top:80px;">No components on this tab.</div>';
1292
+ return;
1293
+ }
1294
+ const r = e.clientWidth || this.root.clientWidth || 800, s = t.map((p) => p.position), o = s.length ? Math.min(...s.map((p) => p.y)) : 0, i = new Q(r, o);
1295
+ e.style.height = `${Q.totalHeight(s)}px`, e.innerHTML = "";
1296
+ const a = 36, f = this.inlineFiltersAsDefinitions(), h = /* @__PURE__ */ new Map();
1297
+ for (const p of f)
1298
+ for (const n of (m = p.targetComponents) != null ? m : [])
1299
+ h.has(n) || h.set(n, []), h.get(n).push(p);
1300
+ const v = [];
1301
+ t.forEach((p) => {
1302
+ var d, c, $, w, k;
1303
+ const n = document.createElement("div");
1304
+ if (n.className = "ds-component-wrapper", n.dataset.componentId = p.id, i.applyStyles(n, p.position), !this.opts.hideBackground) {
1305
+ const x = ((c = (d = p.properties) == null ? void 0 : d.styleConfig) == null ? void 0 : c.backgroundColor) || (($ = p.properties) == null ? void 0 : $.backgroundColor);
1306
+ x && (n.style.background = x);
1307
+ }
1308
+ e.appendChild(n);
1309
+ const g = i.toPx(p.position), u = Math.max(50, g.height - a), b = document.createElement("div");
1310
+ b.style.cssText = "width:100%;height:100%;", n.appendChild(b);
1311
+ const l = this.getComponentDescription(p);
1312
+ if (l) {
1313
+ const x = this.getTooltipEl();
1314
+ n.addEventListener("mouseenter", () => {
1315
+ x.textContent = l, x.style.display = "block";
1316
+ const T = n.getBoundingClientRect(), y = x.offsetHeight || 36;
1317
+ window.innerHeight - T.bottom >= y + 6 ? x.style.top = `${T.bottom + 4}px` : x.style.top = `${T.top - y - 4}px`, x.style.left = `${T.left}px`, x.style.width = `${T.width}px`;
1318
+ }), n.addEventListener("mouseleave", () => {
1319
+ x.style.display = "none";
1320
+ });
1321
+ }
1322
+ if (!this.opts.hideInlineFilters) {
1323
+ const x = [
1324
+ ...(w = h.get(p.id)) != null ? w : [],
1325
+ ...(k = h.get("__all__")) != null ? k : []
1326
+ ].filter((T, y, C) => C.findIndex((I) => I.filterId === T.filterId) === y);
1327
+ x.length > 0 && this.attachInlineFilterBtn(n, x);
1328
+ }
1329
+ v.push(
1330
+ this.renderComponent(b, p, u).catch((x) => {
1331
+ console.error(`[Deepspot SDK] Failed to render component ${p.id} (${p.type}):`, x), b.innerHTML = `<div style="padding:8px;color:#ef4444;font-size:12px;">⚠ ${p.type} render error</div>`;
1332
+ })
1333
+ );
1334
+ }), Promise.all(v).catch(() => {
1335
+ });
1336
+ }
1337
+ // ── Component renderer switch ─────────────────────────────────────────────
1338
+ async renderComponent(e, t, r) {
1339
+ var i, a, f, h, v, m, p, n, g, u, b, l, d, c, $, w, k, x, T, y, C, I, E, N, M, H, B, K, G, _, U, W, Y, J, X;
1340
+ const s = this.renderData.data[t.id] || [], o = this.opts.theme;
1341
+ switch (t.type) {
1342
+ case "bar":
1343
+ case "line":
1344
+ case "pie":
1345
+ case "donut":
1346
+ case "area":
1347
+ case "scatter":
1348
+ case "stacked-bar": {
1349
+ const F = new re(e);
1350
+ await F.render(t, s, o, r), this.chartRenderers.set(t.id, F);
1351
+ break;
1352
+ }
1353
+ case "table": {
1354
+ const F = this.opts.onFetchTablePage ? (L, A) => this.opts.onFetchTablePage(t.id, L, A) : void 0;
1355
+ this.tableRenderer.render(e, t, s, F);
1356
+ break;
1357
+ }
1358
+ case "number-card":
1359
+ this.cardRenderer.render(e, t, s);
1360
+ break;
1361
+ case "text-heading": {
1362
+ if (this.opts.hideText) break;
1363
+ const F = ((a = (i = t.properties) == null ? void 0 : i.data) == null ? void 0 : a.dataContent) || ((f = t.properties) == null ? void 0 : f.content) || t.title || "", L = ((h = t.properties) == null ? void 0 : h.styleConfig) || {}, A = this.scToInlineStyle(L), S = this.opts.hideBackground ? {} : this.parseTwColor((m = (v = t.properties) == null ? void 0 : v.color) != null ? m : ""), O = [[
1364
+ S.bg ? `background:${S.bg}` : "",
1365
+ S.text ? `color:${S.text}` : ""
1366
+ ].filter(Boolean).join(";"), A].filter(Boolean).join(";");
1367
+ e.innerHTML = `<div class="ds-text-heading" style="${O}">${F}</div>`;
1368
+ break;
1369
+ }
1370
+ case "text-subheading": {
1371
+ if (this.opts.hideText) break;
1372
+ const F = ((n = (p = t.properties) == null ? void 0 : p.data) == null ? void 0 : n.dataContent) || ((g = t.properties) == null ? void 0 : g.content) || t.title || "", L = ((u = t.properties) == null ? void 0 : u.styleConfig) || {}, A = this.scToInlineStyle(L), S = this.opts.hideBackground ? {} : this.parseTwColor((l = (b = t.properties) == null ? void 0 : b.color) != null ? l : ""), O = [[
1373
+ S.bg ? `background:${S.bg}` : "",
1374
+ S.text ? `color:${S.text}` : ""
1375
+ ].filter(Boolean).join(";"), A].filter(Boolean).join(";");
1376
+ e.innerHTML = `<div class="ds-text-subheading" style="${O}">${F}</div>`;
1377
+ break;
1378
+ }
1379
+ case "text-body": {
1380
+ if (this.opts.hideText) break;
1381
+ const F = ((c = (d = t.properties) == null ? void 0 : d.data) == null ? void 0 : c.dataContent) || (($ = t.properties) == null ? void 0 : $.content) || "", L = ((w = t.properties) == null ? void 0 : w.styleConfig) || {}, A = this.scToInlineStyle(L), S = this.opts.hideBackground ? {} : this.parseTwColor((x = (k = t.properties) == null ? void 0 : k.color) != null ? x : ""), O = [[
1382
+ S.bg ? `background:${S.bg}` : "",
1383
+ S.text ? `color:${S.text}` : ""
1384
+ ].filter(Boolean).join(";"), A].filter(Boolean).join(";");
1385
+ e.innerHTML = `<div class="ds-text-body" style="${O}">${F}</div>`;
1386
+ break;
1387
+ }
1388
+ case "text-box": {
1389
+ if (this.opts.hideText) break;
1390
+ const F = ((y = (T = t.properties) == null ? void 0 : T.data) == null ? void 0 : y.dataContent) || ((C = t.properties) == null ? void 0 : C.content) || "", L = ((I = t.properties) == null ? void 0 : I.styleConfig) || {}, A = this.scToInlineStyle(L), S = this.opts.hideBackground ? {} : this.parseTwColor((N = (E = t.properties) == null ? void 0 : E.color) != null ? N : ""), O = [[
1391
+ S.bg ? `background:${S.bg}` : "",
1392
+ S.text ? `color:${S.text}` : ""
1393
+ ].filter(Boolean).join(";"), A].filter(Boolean).join(";");
1394
+ e.innerHTML = `<div class="ds-text-box" style="${O}">${F}</div>`;
1395
+ break;
1396
+ }
1397
+ case "text": {
1398
+ if (this.opts.hideText) break;
1399
+ const F = ((H = (M = t.properties) == null ? void 0 : M.data) == null ? void 0 : H.dataContent) || ((B = t.properties) == null ? void 0 : B.content) || "", L = ((K = t.properties) == null ? void 0 : K.styleConfig) || {}, A = this.scToInlineStyle(L), S = this.opts.hideBackground ? {} : this.parseTwColor((_ = (G = t.properties) == null ? void 0 : G.color) != null ? _ : ""), O = [[
1400
+ S.bg ? `background:${S.bg}` : "",
1401
+ S.text ? `color:${S.text}` : ""
1402
+ ].filter(Boolean).join(";"), A].filter(Boolean).join(";");
1403
+ e.innerHTML = `<div class="ds-text-body" style="${O}">${F}</div>`;
1404
+ break;
1405
+ }
1406
+ case "header": {
1407
+ if (this.opts.hideHeader) break;
1408
+ const F = ((W = (U = t.properties) == null ? void 0 : U.data) == null ? void 0 : W.dataContent) || ((Y = t.properties) == null ? void 0 : Y.content) || t.title || "", L = ((J = t.properties) == null ? void 0 : J.styleConfig) || {}, A = !this.opts.hideBackground && L.backgroundColor ? `background:${L.backgroundColor};` : "", S = L.color ? `color:${L.color};` : L.textColor ? `color:${L.textColor};` : "";
1409
+ e.innerHTML = `
1410
+ <div class="ds-header-component" style="${A}${S}">
1411
+ <div class="ds-header-content">${F}</div>
1412
+ </div>`;
1413
+ break;
1414
+ }
1415
+ case "section": {
1416
+ const F = ((X = t.properties) == null ? void 0 : X.styleConfig) || {}, L = !this.opts.hideBackground && F.backgroundColor ? `background:${F.backgroundColor};` : "";
1417
+ e.innerHTML = `
1418
+ <div class="ds-section-wrapper" style="${L}">
1419
+ ${t.title ? `<div class="ds-section-title">${t.title}</div>` : ""}
1420
+ </div>`;
1421
+ break;
1422
+ }
1423
+ }
1424
+ }
1425
+ // ── Inline-filter funnel button on chart wrappers ────────────────────────
1426
+ attachInlineFilterBtn(e, t) {
1427
+ var a;
1428
+ e.style.overflow = "visible";
1429
+ const r = (a = e.dataset.componentId) != null ? a : "", s = document.createElement("button");
1430
+ s.type = "button", s.className = "ds-comp-filter-btn", s.title = "Filter", s.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
1431
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1432
+ <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
1433
+ </svg>`;
1434
+ const o = document.createElement("div");
1435
+ o.className = "ds-comp-filter-pop", o.style.display = "none", this.buildInlineFilterPop(o, t, r), s.addEventListener("click", (f) => {
1436
+ f.stopPropagation();
1437
+ const h = o.style.display !== "none";
1438
+ document.querySelectorAll(".ds-comp-filter-pop").forEach((v) => {
1439
+ v.style.display = "none";
1440
+ }), o.style.display = h ? "none" : "";
1441
+ }), document.addEventListener("mousedown", (f) => {
1442
+ e.contains(f.target) || (o.style.display = "none");
1443
+ }), (() => {
1444
+ const f = t.some((h) => {
1445
+ const v = h.applyToField || h.filterId, m = this.activeFilters[v] !== void 0 && this.activeFilters[v] !== "", p = this.activeLocalFilters[h.filterId] !== void 0;
1446
+ return m || p;
1447
+ });
1448
+ s.classList.toggle("ds-comp-filter-btn-active", f);
1449
+ })(), e.appendChild(s), e.appendChild(o);
1450
+ }
1451
+ buildInlineFilterPop(e, t, r) {
1452
+ var g, u, b;
1453
+ const s = (l) => {
1454
+ var $;
1455
+ const d = (this.renderData.components || []).find(
1456
+ (w) => w.type === "inline-filter" && w.id === l.filterId
1457
+ ), c = ($ = d == null ? void 0 : d.properties) == null ? void 0 : $.columnMappings;
1458
+ return (c == null ? void 0 : c[r]) || l.applyToField || l.filterId;
1459
+ }, o = (l) => l.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), i = (l) => l.replace(/"/g, "&quot;").replace(/'/g, "&#39;"), a = [
1460
+ { value: "=", label: "Equals (=)" },
1461
+ { value: "!=", label: "Not Equals (!=)" },
1462
+ { value: "LIKE", label: "Contains" },
1463
+ { value: "NOT LIKE", label: "Not Contains" },
1464
+ { value: "STARTS_WITH", label: "Starts With" },
1465
+ { value: "IN", label: "In (Multiple)" },
1466
+ { value: "NOT IN", label: "Not In" },
1467
+ { value: ">", label: "Greater Than (>)" },
1468
+ { value: ">=", label: "Greater or Equal (>=)" },
1469
+ { value: "<", label: "Less Than (<)" },
1470
+ { value: "<=", label: "Less or Equal (<=)" },
1471
+ { value: "IS NULL", label: "Is Empty" },
1472
+ { value: "IS NOT NULL", label: "Is Not Empty" }
1473
+ ], f = /* @__PURE__ */ new Map();
1474
+ for (const l of t) {
1475
+ const d = this.activeLocalFilters[l.filterId], c = (g = d == null ? void 0 : d.value) != null ? g : this.activeFilters[s(l)], $ = c ? Array.isArray(c) ? c.map(String) : [String(c)] : [], w = (d == null ? void 0 : d.operator) || l.filterOperator || "=";
1476
+ f.set(l.filterId, { pendingValues: [...$], operator: w, search: "" });
1477
+ }
1478
+ const h = (l) => `<select class="ds-fp-op-select">
1479
+ ${a.map(
1480
+ (d) => `<option value="${i(d.value)}"${d.value === l ? " selected" : ""}>${d.label}</option>`
1481
+ ).join("")}
1482
+ </select>`, v = (l, d) => {
1483
+ const c = l.options || [];
1484
+ if (!c.length)
1485
+ return '<div class="ds-fp-opts-msg"><span class="ds-dd-spinner"></span> Loading…</div>';
1486
+ const $ = d.search.toLowerCase(), w = c.filter(
1487
+ (k) => (typeof k == "string" ? k : k.label).toLowerCase().includes($)
1488
+ );
1489
+ return w.length ? w.map((k) => {
1490
+ const x = typeof k == "string" ? k : k.value, T = typeof k == "string" ? k : k.label, y = d.pendingValues.includes(x);
1491
+ return `<label class="ds-fp-opt${y ? " ds-fp-opt-on" : ""}" data-val="${i(x)}">
1492
+ <span class="ds-fp-cb${y ? " ds-fp-cb-on" : ""}">
1493
+ ${y ? `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"
1494
+ stroke-linecap="round" stroke-linejoin="round">
1495
+ <polyline points="20 6 9 17 4 12"/>
1496
+ </svg>` : ""}
1497
+ </span>
1498
+ <span class="ds-fp-opt-lbl">${o(T)}</span>
1499
+ </label>`;
1500
+ }).join("") : '<div class="ds-fp-opts-msg">No matches</div>';
1501
+ }, m = (l) => {
1502
+ const d = f.get(l.filterId);
1503
+ return `
1504
+ <div class="ds-fp-item ds-fp-item-expanded" data-filter-id="${i(l.filterId)}">
1505
+ <div class="ds-fp-item-hd">
1506
+ <span class="ds-fp-item-name">${o(l.label)}</span>
1507
+ <svg class="ds-fp-chevron" style="transform:rotate(180deg)"
1508
+ viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
1509
+ stroke-linecap="round" stroke-linejoin="round">
1510
+ <polyline points="6 9 12 15 18 9"/>
1511
+ </svg>
1512
+ </div>
1513
+ <div class="ds-fp-item-body">
1514
+ <div class="ds-fp-op-row">
1515
+ <span class="ds-fp-op-lbl">Operator</span>
1516
+ ${h(d.operator)}
1517
+ </div>
1518
+ <div class="ds-fp-ms">
1519
+ ${d.pendingValues.length ? `<div class="ds-fp-chips-row">
1520
+ ${d.pendingValues.map(
1521
+ (c) => `<span class="ds-fp-chip">${o(c)}<button type="button" class="ds-fp-chip-x" data-val="${i(c)}">✕</button></span>`
1522
+ ).join("")}
1523
+ </div>` : ""}
1524
+ <div class="ds-fp-search-wrap">
1525
+ <input type="text" class="ds-fp-ms-search"
1526
+ placeholder="Search ${i(l.label)}…"
1527
+ value="${i(d.search)}"/>
1528
+ <svg class="ds-fp-search-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor"
1529
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1530
+ <polyline points="18 15 12 9 6 15"/>
1531
+ </svg>
1532
+ </div>
1533
+ <div class="ds-fp-opts" data-filter-id="${i(l.filterId)}">
1534
+ ${v(l, d)}
1535
+ </div>
1536
+ </div>
1537
+ <button type="button" class="ds-fp-apply" data-apply-id="${i(l.filterId)}">✓ Apply Filter</button>
1538
+ </div>
1539
+ </div>`;
1540
+ }, p = t.filter((l) => {
1541
+ if (this.activeLocalFilters[l.filterId] !== void 0) return !0;
1542
+ const d = this.activeFilters[s(l)];
1543
+ return d !== void 0 && d !== "" && !(Array.isArray(d) && d.length === 0);
1544
+ }).length;
1545
+ e.innerHTML = `
1546
+ <div class="ds-fp-panel ds-fp-panel-pop">
1547
+ <div class="ds-fp-panel-hd">
1548
+ <div class="ds-fp-panel-title">
1549
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
1550
+ stroke-linecap="round" stroke-linejoin="round">
1551
+ <line x1="3" y1="6" x2="21" y2="6"/>
1552
+ <line x1="3" y1="12" x2="21" y2="12"/>
1553
+ <line x1="3" y1="18" x2="21" y2="18"/>
1554
+ </svg>
1555
+ <span>Filters</span>
1556
+ </div>
1557
+ <button type="button" class="ds-fp-close-btn ds-pop-close">
1558
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
1559
+ stroke-linecap="round" stroke-linejoin="round">
1560
+ <line x1="18" y1="6" x2="6" y2="18"/>
1561
+ <line x1="6" y1="6" x2="18" y2="18"/>
1562
+ </svg>
1563
+ </button>
1564
+ </div>
1565
+ <div class="ds-fp-reset-section" style="display:${p ? "" : "none"}">
1566
+ <button type="button" class="ds-fp-reset-all ds-pop-reset">
1567
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
1568
+ stroke-linecap="round" stroke-linejoin="round">
1569
+ <polyline points="1 4 1 10 7 10"/>
1570
+ <path d="M3.51 15a9 9 0 1 0 .49-3.5"/>
1571
+ </svg>
1572
+ Reset All Filters
1573
+ </button>
1574
+ </div>
1575
+ <div class="ds-fp-items">
1576
+ ${t.map(m).join("")}
1577
+ </div>
1578
+ </div>`, (u = e.querySelector(".ds-pop-close")) == null || u.addEventListener("click", () => {
1579
+ e.style.display = "none";
1580
+ }), (b = e.querySelector(".ds-pop-reset")) == null || b.addEventListener("click", () => {
1581
+ var l, d;
1582
+ t.forEach((c) => {
1583
+ delete this.activeLocalFilters[c.filterId];
1584
+ }), (d = (l = this.opts).onFilterChange) == null || d.call(l, this.activeFilters), this.opts.onTabSwitch(this.activePageId, this.activeTabId), e.style.display = "none";
1585
+ });
1586
+ const n = (l, d, c) => {
1587
+ const $ = t.find((x) => x.filterId === l), w = d.querySelector(".ds-fp-ms");
1588
+ if (w) {
1589
+ let x = w.querySelector(".ds-fp-chips-row");
1590
+ if (c.pendingValues.length) {
1591
+ const T = c.pendingValues.map(
1592
+ (y) => `<span class="ds-fp-chip">${o(y)}<button type="button" class="ds-fp-chip-x" data-val="${i(y)}">✕</button></span>`
1593
+ ).join("");
1594
+ x ? x.innerHTML = T : (x = document.createElement("div"), x.className = "ds-fp-chips-row", x.innerHTML = T, w.insertBefore(x, w.querySelector(".ds-fp-search-wrap"))), x.querySelectorAll(".ds-fp-chip-x").forEach((y) => {
1595
+ y.addEventListener("click", (C) => {
1596
+ C.stopPropagation(), c.pendingValues = c.pendingValues.filter((I) => {
1597
+ var E;
1598
+ return I !== ((E = y.dataset.val) != null ? E : "");
1599
+ }), n(l, d, c);
1600
+ });
1601
+ });
1602
+ } else
1603
+ x == null || x.remove();
1604
+ }
1605
+ const k = d.querySelector(`.ds-fp-opts[data-filter-id="${l}"]`);
1606
+ k && (k.innerHTML = v($, c), k.querySelectorAll(".ds-fp-opt").forEach((x) => {
1607
+ x.addEventListener("click", () => {
1608
+ var y;
1609
+ const T = (y = x.dataset.val) != null ? y : "";
1610
+ c.pendingValues.includes(T) ? c.pendingValues = c.pendingValues.filter((C) => C !== T) : c.pendingValues = [...c.pendingValues, T], n(l, d, c);
1611
+ });
1612
+ }));
1613
+ };
1614
+ t.forEach((l) => {
1615
+ var w, k, x, T;
1616
+ const d = f.get(l.filterId), c = e.querySelector(`.ds-fp-item[data-filter-id="${l.filterId}"]`);
1617
+ if (!c) return;
1618
+ (w = c.querySelector(".ds-fp-op-select")) == null || w.addEventListener("change", (y) => {
1619
+ d.operator = y.target.value;
1620
+ });
1621
+ const $ = c.querySelector(".ds-fp-ms-search");
1622
+ $ && ($.addEventListener("input", () => {
1623
+ d.search = $.value, n(l.filterId, c, d);
1624
+ }), (k = c.querySelector(".ds-fp-search-caret")) == null || k.addEventListener("click", () => {
1625
+ const y = c.querySelector(".ds-fp-opts");
1626
+ y && (y.style.display = y.style.display === "none" ? "" : "none");
1627
+ })), c.querySelectorAll(".ds-fp-chip-x").forEach((y) => {
1628
+ y.addEventListener("click", (C) => {
1629
+ C.stopPropagation(), d.pendingValues = d.pendingValues.filter((I) => {
1630
+ var E;
1631
+ return I !== ((E = y.dataset.val) != null ? E : "");
1632
+ }), n(l.filterId, c, d);
1633
+ });
1634
+ }), c.querySelectorAll(".ds-fp-opt").forEach((y) => {
1635
+ y.addEventListener("click", () => {
1636
+ var I;
1637
+ const C = (I = y.dataset.val) != null ? I : "";
1638
+ d.pendingValues.includes(C) ? d.pendingValues = d.pendingValues.filter((E) => E !== C) : d.pendingValues = [...d.pendingValues, C], n(l.filterId, c, d);
1639
+ });
1640
+ }), (x = c.querySelector(`.ds-fp-apply[data-apply-id="${l.filterId}"]`)) == null || x.addEventListener("click", () => {
1641
+ var C, I, E;
1642
+ const y = d.pendingValues.length > 1 ? [...d.pendingValues] : (C = d.pendingValues[0]) != null ? C : "";
1643
+ !y || Array.isArray(y) && !y.length ? delete this.activeLocalFilters[l.filterId] : this.activeLocalFilters[l.filterId] = { value: y, operator: d.operator || "=" }, (E = (I = this.opts).onFilterChange) == null || E.call(I, this.activeFilters), this.opts.onTabSwitch(this.activePageId, this.activeTabId), e.style.display = "none";
1644
+ }), !((T = l.options) != null && T.length) && this.opts.onFetchFilterOptions && this.opts.onFetchFilterOptions(l.filterId).then((y) => {
1645
+ c.querySelector(`.ds-fp-opts[data-filter-id="${l.filterId}"]`) && (l.options = y.map((I) => typeof I == "string" ? I : I.value), n(l.filterId, c, d));
1646
+ }).catch(() => {
1647
+ });
1648
+ });
1649
+ }
1650
+ // ── Spinner ───────────────────────────────────────────────────────────────
1651
+ showTabSpinner() {
1652
+ const e = this.root.querySelector("#ds-tab-loading"), t = this.root.querySelector("#ds-grid");
1653
+ e && (e.style.display = "flex"), t && (t.style.opacity = "0.35");
1654
+ }
1655
+ hideTabSpinner() {
1656
+ const e = this.root.querySelector("#ds-tab-loading"), t = this.root.querySelector("#ds-grid");
1657
+ e && (e.style.display = "none"), t && (t.style.opacity = "1");
1658
+ }
1659
+ // ── Helpers ───────────────────────────────────────────────────────────────
1660
+ /**
1661
+ * Convert a styleConfig object to an inline CSS string.
1662
+ * Maps camelCase keys (e.g. fontSize) to kebab-case CSS properties.
1663
+ * Skips backgroundColor — that is applied to the wrapper div separately
1664
+ * (so it can support CSS gradients via `background` shorthand).
1665
+ */
1666
+ scToInlineStyle(e) {
1667
+ const t = /* @__PURE__ */ new Set(["backgroundColor"]), r = (s) => s.replace(/([A-Z])/g, "-$1").toLowerCase();
1668
+ return Object.entries(e).filter(([s, o]) => !t.has(s) && o != null && o !== "").map(([s, o]) => `${r(s)}:${o}`).join(";");
1669
+ }
1670
+ /**
1671
+ * Parse a Tailwind utility class string (e.g. "bg-violet-100 text-violet-600")
1672
+ * into plain CSS hex values. Used for text components whose builder color
1673
+ * is stored as Tailwind class names rather than raw CSS.
1674
+ */
1675
+ parseTwColor(e) {
1676
+ if (!e) return {};
1677
+ const t = {};
1678
+ for (const r of e.split(/\s+/)) {
1679
+ if (!r) continue;
1680
+ if (r === "bg-white") {
1681
+ t.bg = "#ffffff";
1682
+ continue;
1683
+ }
1684
+ if (r === "bg-black") {
1685
+ t.bg = "#000000";
1686
+ continue;
1687
+ }
1688
+ if (r === "bg-transparent") {
1689
+ t.bg = "transparent";
1690
+ continue;
1691
+ }
1692
+ if (r === "text-white") {
1693
+ t.text = "#ffffff";
1694
+ continue;
1695
+ }
1696
+ if (r === "text-black") {
1697
+ t.text = "#000000";
1698
+ continue;
1699
+ }
1700
+ const s = /^(bg|text)-([a-z]+)-(\d+)$/.exec(r);
1701
+ if (!s) continue;
1702
+ const o = z.TW_PALETTE[s[2]], i = o == null ? void 0 : o[Number(s[3])];
1703
+ i && (s[1] === "bg" && (t.bg = i), s[1] === "text" && (t.text = i));
1704
+ }
1705
+ return t;
1706
+ }
1707
+ getTooltipEl() {
1708
+ if (!this.tooltipEl) {
1709
+ const e = document.createElement("div");
1710
+ e.className = "ds-global-tooltip", e.style.display = "none", document.body.appendChild(e), this.tooltipEl = e;
1711
+ }
1712
+ return this.tooltipEl;
1713
+ }
1714
+ getComponentDescription(e) {
1715
+ var t, r, s, o;
1716
+ if ((t = e.properties) != null && t.description) return String(e.properties.description);
1717
+ try {
1718
+ const i = (s = (r = e.properties) == null ? void 0 : r.data) == null ? void 0 : s.dataContent;
1719
+ if (i) {
1720
+ const a = typeof i == "string" ? JSON.parse(i) : i;
1721
+ return (a == null ? void 0 : a.description) || ((o = a == null ? void 0 : a.config) == null ? void 0 : o.description) || "";
1722
+ }
1723
+ } catch (i) {
1724
+ }
1725
+ return "";
1726
+ }
1727
+ getTabsForPage(e) {
1728
+ var t, r;
1729
+ return (r = (t = this.renderData.dashboard.pages.find((s) => s.pageId === e)) == null ? void 0 : t.tabs) != null ? r : [];
1730
+ }
1731
+ };
1732
+ z.TW_PALETTE = {
1733
+ slate: { 50: "#f8fafc", 100: "#f1f5f9", 200: "#e2e8f0", 300: "#cbd5e1", 400: "#94a3b8", 500: "#64748b", 600: "#475569", 700: "#334155", 800: "#1e293b", 900: "#0f172a" },
1734
+ gray: { 50: "#f9fafb", 100: "#f3f4f6", 200: "#e5e7eb", 300: "#d1d5db", 400: "#9ca3af", 500: "#6b7280", 600: "#4b5563", 700: "#374151", 800: "#1f2937", 900: "#111827" },
1735
+ zinc: { 50: "#fafafa", 100: "#f4f4f5", 200: "#e4e4e7", 300: "#d4d4d8", 400: "#a1a1aa", 500: "#71717a", 600: "#52525b", 700: "#3f3f46", 800: "#27272a", 900: "#18181b" },
1736
+ neutral: { 50: "#fafafa", 100: "#f5f5f5", 200: "#e5e5e5", 300: "#d4d4d4", 400: "#a3a3a3", 500: "#737373", 600: "#525252", 700: "#404040", 800: "#262626", 900: "#171717" },
1737
+ stone: { 50: "#fafaf9", 100: "#f5f5f4", 200: "#e7e5e4", 300: "#d6d3d1", 400: "#a8a29e", 500: "#78716c", 600: "#57534e", 700: "#44403c", 800: "#292524", 900: "#1c1917" },
1738
+ red: { 50: "#fef2f2", 100: "#fee2e2", 200: "#fecaca", 300: "#fca5a5", 400: "#f87171", 500: "#ef4444", 600: "#dc2626", 700: "#b91c1c", 800: "#991b1b", 900: "#7f1d1d" },
1739
+ orange: { 50: "#fff7ed", 100: "#ffedd5", 200: "#fed7aa", 300: "#fdba74", 400: "#fb923c", 500: "#f97316", 600: "#ea580c", 700: "#c2410c", 800: "#9a3412", 900: "#7c2d12" },
1740
+ amber: { 50: "#fffbeb", 100: "#fef3c7", 200: "#fde68a", 300: "#fcd34d", 400: "#fbbf24", 500: "#f59e0b", 600: "#d97706", 700: "#b45309", 800: "#92400e", 900: "#78350f" },
1741
+ yellow: { 50: "#fefce8", 100: "#fef9c3", 200: "#fef08a", 300: "#fde047", 400: "#facc15", 500: "#eab308", 600: "#ca8a04", 700: "#a16207", 800: "#854d0e", 900: "#713f12" },
1742
+ lime: { 50: "#f7fee7", 100: "#ecfccb", 200: "#d9f99d", 300: "#bef264", 400: "#a3e635", 500: "#84cc16", 600: "#65a30d", 700: "#4d7c0f", 800: "#3f6212", 900: "#365314" },
1743
+ green: { 50: "#f0fdf4", 100: "#dcfce7", 200: "#bbf7d0", 300: "#86efac", 400: "#4ade80", 500: "#22c55e", 600: "#16a34a", 700: "#15803d", 800: "#166534", 900: "#14532d" },
1744
+ emerald: { 50: "#ecfdf5", 100: "#d1fae5", 200: "#a7f3d0", 300: "#6ee7b7", 400: "#34d399", 500: "#10b981", 600: "#059669", 700: "#047857", 800: "#065f46", 900: "#064e3b" },
1745
+ teal: { 50: "#f0fdfa", 100: "#ccfbf1", 200: "#99f6e4", 300: "#5eead4", 400: "#2dd4bf", 500: "#14b8a6", 600: "#0d9488", 700: "#0f766e", 800: "#115e59", 900: "#134e4a" },
1746
+ cyan: { 50: "#ecfeff", 100: "#cffafe", 200: "#a5f3fc", 300: "#67e8f9", 400: "#22d3ee", 500: "#06b6d4", 600: "#0891b2", 700: "#0e7490", 800: "#155e75", 900: "#164e63" },
1747
+ sky: { 50: "#f0f9ff", 100: "#e0f2fe", 200: "#bae6fd", 300: "#7dd3fc", 400: "#38bdf8", 500: "#0ea5e9", 600: "#0284c7", 700: "#0369a1", 800: "#075985", 900: "#0c4a6e" },
1748
+ blue: { 50: "#eff6ff", 100: "#dbeafe", 200: "#bfdbfe", 300: "#93c5fd", 400: "#60a5fa", 500: "#3b82f6", 600: "#2563eb", 700: "#1d4ed8", 800: "#1e40af", 900: "#1e3a8a" },
1749
+ indigo: { 50: "#eef2ff", 100: "#e0e7ff", 200: "#c7d2fe", 300: "#a5b4fc", 400: "#818cf8", 500: "#6366f1", 600: "#4f46e5", 700: "#4338ca", 800: "#3730a3", 900: "#312e81" },
1750
+ violet: { 50: "#f5f3ff", 100: "#ede9fe", 200: "#ddd6fe", 300: "#c4b5fd", 400: "#a78bfa", 500: "#8b5cf6", 600: "#7c3aed", 700: "#6d28d9", 800: "#5b21b6", 900: "#4c1d95" },
1751
+ purple: { 50: "#faf5ff", 100: "#f3e8ff", 200: "#e9d5ff", 300: "#d8b4fe", 400: "#c084fc", 500: "#a855f7", 600: "#9333ea", 700: "#7e22ce", 800: "#6b21a8", 900: "#581c87" },
1752
+ fuchsia: { 50: "#fdf4ff", 100: "#fae8ff", 200: "#f5d0fe", 300: "#f0abfc", 400: "#e879f9", 500: "#d946ef", 600: "#c026d3", 700: "#a21caf", 800: "#86198f", 900: "#701a75" },
1753
+ pink: { 50: "#fdf2f8", 100: "#fce7f3", 200: "#fbcfe8", 300: "#f9a8d4", 400: "#f472b6", 500: "#ec4899", 600: "#db2777", 700: "#be185d", 800: "#9d174d", 900: "#831843" },
1754
+ rose: { 50: "#fff1f2", 100: "#ffe4e6", 200: "#fecdd3", 300: "#fda4af", 400: "#fb7185", 500: "#f43f5e", 600: "#e11d48", 700: "#be123c", 800: "#9f1239", 900: "#881337" }
1755
+ };
1756
+ let D = z;
1757
+ class ce {
1758
+ constructor(e, t) {
1759
+ this.chartRenderer = null, this.tableRenderer = new se(), this.cardRenderer = new oe(), this.root = e, this.theme = t;
1760
+ }
1761
+ render(e, t) {
1762
+ const r = e.components[0];
1763
+ if (!r) {
1764
+ this.root.innerHTML = `<div class="ds-embed-error">
1765
+ <div class="ds-embed-error-icon">⚠</div>
1766
+ <div>Component not found.</div>
1767
+ </div>`;
1768
+ return;
1769
+ }
1770
+ const s = e.data[r.id] || [];
1771
+ this.renderComponent(r, s), t == null || t();
1772
+ }
1773
+ update(e) {
1774
+ const t = e.components[0];
1775
+ if (!t) return;
1776
+ const r = e.data[t.id] || [];
1777
+ switch (t.type) {
1778
+ case "bar":
1779
+ case "line":
1780
+ case "pie":
1781
+ case "donut":
1782
+ case "area":
1783
+ this.chartRenderer ? this.chartRenderer.update(t, r, this.theme) : this.renderComponent(t, r);
1784
+ break;
1785
+ case "table":
1786
+ this.tableRenderer.render(this.root, t, r);
1787
+ break;
1788
+ case "number-card":
1789
+ this.cardRenderer.render(this.root, t, r);
1790
+ break;
1791
+ }
1792
+ }
1793
+ destroy() {
1794
+ var e;
1795
+ (e = this.chartRenderer) == null || e.destroy(), this.chartRenderer = null, this.root.innerHTML = "";
1796
+ }
1797
+ renderComponent(e, t) {
1798
+ var r;
1799
+ switch (e.type) {
1800
+ case "bar":
1801
+ case "line":
1802
+ case "pie":
1803
+ case "donut":
1804
+ case "area":
1805
+ (r = this.chartRenderer) == null || r.destroy(), this.chartRenderer = new re(this.root), this.chartRenderer.render(e, t, this.theme);
1806
+ break;
1807
+ case "table":
1808
+ this.tableRenderer.render(this.root, e, t);
1809
+ break;
1810
+ case "number-card":
1811
+ this.cardRenderer.render(this.root, e, t);
1812
+ break;
1813
+ default:
1814
+ this.root.innerHTML = `<div class="ds-chart-empty">Unsupported component type: ${e.type}</div>`;
1815
+ }
1816
+ }
1817
+ }
1818
+ let ee = !1;
1819
+ function pe() {
1820
+ if (!(ee || typeof document == "undefined")) {
1821
+ ee = !0;
1822
+ try {
1823
+ const P = `/* ─────────────────────────────────────────────────────────────────────────────
1824
+ Deepspot SDK — Base Embed Styles
1825
+ Injected once into the host page's <head> by the SDK.
1826
+ All selectors are scoped under .ds-embed-* to avoid leaking into host styles.
1827
+ ───────────────────────────────────────────────────────────────────────────── */
1828
+
1829
+ /* ── Reset inside SDK containers ──────────────────────────────────────────── */
1830
+ .ds-embed-root *,
1831
+ .ds-embed-root *::before,
1832
+ .ds-embed-root *::after {
1833
+ box-sizing: border-box;
1834
+ margin: 0;
1835
+ padding: 0;
1836
+ }
1837
+
1838
+ /* ── Root container ────────────────────────────────────────────────────────── */
1839
+ .ds-embed-root {
1840
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
1841
+ Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
1842
+ font-size: 14px;
1843
+ line-height: 1.5;
1844
+ width: 100%;
1845
+ height: 100%;
1846
+ overflow: auto;
1847
+ position: relative;
1848
+ /* Default white background — overridden by page backgroundColor from publish page */
1849
+ background: #ffffff;
1850
+ color: #111827;
1851
+ }
1852
+
1853
+ .ds-embed-root.ds-theme-light {
1854
+ /* No forced background — let component styleConfig backgrounds show through */
1855
+ color: #111827;
1856
+ }
1857
+
1858
+ .ds-embed-root.ds-theme-dark {
1859
+ /* No forced background — let component styleConfig backgrounds show through */
1860
+ color: #f1f5f9;
1861
+ }
1862
+
1863
+ /* ── Loading state ─────────────────────────────────────────────────────────── */
1864
+ .ds-embed-loading {
1865
+ display: flex;
1866
+ flex-direction: column;
1867
+ align-items: center;
1868
+ justify-content: center;
1869
+ height: 100%;
1870
+ min-height: 200px;
1871
+ gap: 12px;
1872
+ color: #6b7280;
1873
+ }
1874
+
1875
+ .ds-embed-spinner {
1876
+ width: 36px;
1877
+ height: 36px;
1878
+ border: 3px solid #e5e7eb;
1879
+ border-top-color: #6366f1;
1880
+ border-radius: 50%;
1881
+ animation: ds-spin 0.8s linear infinite;
1882
+ }
1883
+
1884
+ @keyframes ds-spin {
1885
+ to { transform: rotate(360deg); }
1886
+ }
1887
+
1888
+ .ds-embed-loading-text {
1889
+ font-size: 13px;
1890
+ }
1891
+
1892
+ /* ── Error state ───────────────────────────────────────────────────────────── */
1893
+ .ds-embed-error {
1894
+ display: flex;
1895
+ flex-direction: column;
1896
+ align-items: center;
1897
+ justify-content: center;
1898
+ height: 100%;
1899
+ min-height: 200px;
1900
+ gap: 8px;
1901
+ padding: 24px;
1902
+ text-align: center;
1903
+ color: #ef4444;
1904
+ }
1905
+
1906
+ .ds-embed-error-icon {
1907
+ font-size: 32px;
1908
+ }
1909
+
1910
+ .ds-embed-error-message {
1911
+ font-size: 13px;
1912
+ color: #6b7280;
1913
+ }
1914
+
1915
+ /* ── Filter bar ────────────────────────────────────────────────────────────── */
1916
+ .ds-filter-bar {
1917
+ display: flex;
1918
+ flex-wrap: wrap;
1919
+ align-items: center;
1920
+ gap: 10px;
1921
+ padding: 10px 16px;
1922
+ border-bottom: 1px solid #e5e7eb;
1923
+ background: inherit;
1924
+ }
1925
+
1926
+ .ds-theme-dark .ds-filter-bar {
1927
+ border-bottom-color: #1e293b;
1928
+ }
1929
+
1930
+ .ds-filter-item {
1931
+ display: flex;
1932
+ flex-direction: column;
1933
+ gap: 4px;
1934
+ }
1935
+
1936
+ .ds-filter-label {
1937
+ font-size: 11px;
1938
+ font-weight: 600;
1939
+ text-transform: uppercase;
1940
+ letter-spacing: 0.05em;
1941
+ color: #6b7280;
1942
+ }
1943
+
1944
+ .ds-filter-select,
1945
+ .ds-filter-input {
1946
+ height: 32px;
1947
+ padding: 0 10px;
1948
+ border: 1px solid #d1d5db;
1949
+ border-radius: 6px;
1950
+ font-size: 13px;
1951
+ background: #ffffff;
1952
+ color: #111827;
1953
+ cursor: pointer;
1954
+ min-width: 130px;
1955
+ }
1956
+
1957
+ .ds-theme-dark .ds-filter-select,
1958
+ .ds-theme-dark .ds-filter-input {
1959
+ background: #1e293b;
1960
+ border-color: #334155;
1961
+ color: #f1f5f9;
1962
+ }
1963
+
1964
+ .ds-filter-select:focus,
1965
+ .ds-filter-input:focus {
1966
+ outline: none;
1967
+ border-color: #6366f1;
1968
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
1969
+ }
1970
+
1971
+ .ds-date-range-inputs {
1972
+ display: flex;
1973
+ gap: 6px;
1974
+ align-items: center;
1975
+ }
1976
+
1977
+ .ds-date-range-inputs input[type="date"] {
1978
+ height: 32px;
1979
+ padding: 0 8px;
1980
+ border: 1px solid #d1d5db;
1981
+ border-radius: 6px;
1982
+ font-size: 13px;
1983
+ background: #ffffff;
1984
+ color: #111827;
1985
+ }
1986
+
1987
+ .ds-theme-dark .ds-date-range-inputs input[type="date"] {
1988
+ background: #1e293b;
1989
+ border-color: #334155;
1990
+ color: #f1f5f9;
1991
+ }
1992
+
1993
+ /* ── Page / Tab navigation ─────────────────────────────────────────────────── */
1994
+ .ds-page-nav {
1995
+ display: flex;
1996
+ gap: 4px;
1997
+ padding: 8px 16px 0;
1998
+ border-bottom: 1px solid #e5e7eb;
1999
+ overflow-x: auto;
2000
+ }
2001
+
2002
+ .ds-theme-dark .ds-page-nav {
2003
+ border-bottom-color: #1e293b;
2004
+ }
2005
+
2006
+ .ds-page-tab {
2007
+ padding: 6px 16px;
2008
+ font-size: 13px;
2009
+ font-weight: 500;
2010
+ border: none;
2011
+ border-bottom: 2px solid transparent;
2012
+ background: transparent;
2013
+ color: #6b7280;
2014
+ cursor: pointer;
2015
+ white-space: nowrap;
2016
+ transition: color 0.15s, border-color 0.15s;
2017
+ }
2018
+
2019
+ .ds-page-tab:hover {
2020
+ color: #111827;
2021
+ }
2022
+
2023
+ .ds-theme-dark .ds-page-tab:hover {
2024
+ color: #f1f5f9;
2025
+ }
2026
+
2027
+ .ds-page-tab.ds-active {
2028
+ color: #6366f1;
2029
+ border-bottom-color: #6366f1;
2030
+ }
2031
+
2032
+ /* ── Grid canvas ───────────────────────────────────────────────────────────── */
2033
+ .ds-canvas {
2034
+ position: relative;
2035
+ width: 100%;
2036
+ /* No horizontal padding — grid.clientWidth must equal container width so
2037
+ GridLayout.colWidth = containerWidth/24 matches the dashboard builder.
2038
+ 16px on each side was shrinking the grid by 32px and misaligning positions. */
2039
+ padding: 8px 0 16px;
2040
+ overflow-x: hidden;
2041
+ }
2042
+
2043
+ .ds-grid {
2044
+ position: relative;
2045
+ width: 100%;
2046
+ /* Clip absolutely-positioned children to the grid boundary so rounding
2047
+ errors or miscalculated widths don't create a horizontal scrollbar. */
2048
+ overflow: hidden;
2049
+ }
2050
+
2051
+ .ds-component-wrapper {
2052
+ position: absolute;
2053
+ /* overflow intentionally not set — individual card components handle their own clipping,
2054
+ and tooltip bubbles must be able to overflow the wrapper boundary */
2055
+ }
2056
+
2057
+ /* ── Description tooltip — fixed on body, positioned via JS, never clips content ── */
2058
+ .ds-global-tooltip {
2059
+ position: fixed;
2060
+ z-index: 9999;
2061
+ background: rgba(15, 23, 42, 0.92);
2062
+ color: #f1f5f9;
2063
+ font-size: 12px;
2064
+ line-height: 1.6;
2065
+ padding: 8px 14px;
2066
+ border-radius: 8px;
2067
+ white-space: normal;
2068
+ pointer-events: none;
2069
+ backdrop-filter: blur(4px);
2070
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
2071
+ max-width: 600px;
2072
+ }
2073
+
2074
+ /* ── Chart component ───────────────────────────────────────────────────────── */
2075
+ .ds-chart-card {
2076
+ width: 100%;
2077
+ height: 100%;
2078
+ background: #ffffff;
2079
+ border: 1px solid #e5e7eb;
2080
+ border-radius: 10px;
2081
+ overflow: hidden;
2082
+ display: flex;
2083
+ flex-direction: column;
2084
+ }
2085
+
2086
+ .ds-theme-dark .ds-chart-card {
2087
+ background: #1e293b;
2088
+ border-color: #334155;
2089
+ }
2090
+
2091
+ .ds-chart-title {
2092
+ padding: 10px 14px 4px;
2093
+ font-size: 13px;
2094
+ font-weight: 600;
2095
+ color: #374151;
2096
+ flex-shrink: 0;
2097
+ }
2098
+
2099
+ .ds-theme-dark .ds-chart-title {
2100
+ color: #e2e8f0;
2101
+ }
2102
+
2103
+ .ds-chart-body {
2104
+ flex: 1;
2105
+ min-height: 0;
2106
+ }
2107
+
2108
+ .ds-chart-empty {
2109
+ display: flex;
2110
+ align-items: center;
2111
+ justify-content: center;
2112
+ height: 100%;
2113
+ color: #9ca3af;
2114
+ font-size: 13px;
2115
+ }
2116
+
2117
+ /* ── Table component ───────────────────────────────────────────────────────── */
2118
+ .ds-table-card {
2119
+ width: 100%;
2120
+ height: 100%;
2121
+ background: #ffffff;
2122
+ border: 1px solid #e5e7eb;
2123
+ border-radius: 10px;
2124
+ overflow: hidden;
2125
+ display: flex;
2126
+ flex-direction: column;
2127
+ position: relative;
2128
+ }
2129
+
2130
+ .ds-theme-dark .ds-table-card {
2131
+ background: #1e293b;
2132
+ border-color: #334155;
2133
+ }
2134
+
2135
+ /* ── Table header: dot + title on left, search on right ──────────────────────*/
2136
+ .ds-table-header-row {
2137
+ display: flex;
2138
+ align-items: center;
2139
+ justify-content: space-between;
2140
+ padding: 10px 14px 8px;
2141
+ flex-shrink: 0;
2142
+ gap: 8px;
2143
+ border-bottom: 1px solid #f3f4f6;
2144
+ }
2145
+
2146
+ .ds-theme-dark .ds-table-header-row {
2147
+ border-bottom-color: #334155;
2148
+ }
2149
+
2150
+ .ds-table-title-wrap {
2151
+ display: flex;
2152
+ align-items: center;
2153
+ gap: 6px;
2154
+ min-width: 0;
2155
+ }
2156
+
2157
+ .ds-table-dot {
2158
+ width: 8px;
2159
+ height: 8px;
2160
+ border-radius: 50%;
2161
+ background: #6366f1;
2162
+ flex-shrink: 0;
2163
+ }
2164
+
2165
+ .ds-table-title {
2166
+ font-size: 13px;
2167
+ font-weight: 600;
2168
+ color: #374151;
2169
+ white-space: nowrap;
2170
+ overflow: hidden;
2171
+ text-overflow: ellipsis;
2172
+ }
2173
+
2174
+ .ds-theme-dark .ds-table-title {
2175
+ color: #e2e8f0;
2176
+ }
2177
+
2178
+ /* ── Search ──────────────────────────────────────────────────────────────────*/
2179
+ .ds-table-search-wrap {
2180
+ position: relative;
2181
+ flex-shrink: 0;
2182
+ }
2183
+
2184
+ .ds-table-search-icon {
2185
+ position: absolute;
2186
+ left: 8px;
2187
+ top: 50%;
2188
+ transform: translateY(-50%);
2189
+ width: 14px;
2190
+ height: 14px;
2191
+ color: #9ca3af;
2192
+ pointer-events: none;
2193
+ }
2194
+
2195
+ .ds-table-search {
2196
+ height: 30px;
2197
+ padding: 0 10px 0 28px;
2198
+ border: 1px solid #e5e7eb;
2199
+ border-radius: 6px;
2200
+ font-size: 12px;
2201
+ background: #f9fafb;
2202
+ color: #374151;
2203
+ width: 180px;
2204
+ outline: none;
2205
+ transition: border-color 0.15s, box-shadow 0.15s;
2206
+ }
2207
+
2208
+ .ds-table-search:focus {
2209
+ border-color: #6366f1;
2210
+ box-shadow: 0 0 0 2px rgba(99,102,241,0.15);
2211
+ background: #fff;
2212
+ }
2213
+
2214
+ .ds-theme-dark .ds-table-search {
2215
+ background: #0f172a;
2216
+ border-color: #334155;
2217
+ color: #e2e8f0;
2218
+ }
2219
+
2220
+ .ds-theme-dark .ds-table-search:focus {
2221
+ background: #1e293b;
2222
+ }
2223
+
2224
+ /* ── Scroll area ─────────────────────────────────────────────────────────────*/
2225
+ .ds-table-scroll {
2226
+ flex: 1;
2227
+ overflow: auto;
2228
+ min-height: 0;
2229
+ }
2230
+
2231
+ .ds-table {
2232
+ width: 100%;
2233
+ border-collapse: collapse;
2234
+ font-size: 12px;
2235
+ }
2236
+
2237
+ .ds-table-sno-th {
2238
+ width: 48px;
2239
+ text-align: center !important;
2240
+ }
2241
+
2242
+ .ds-table-sno {
2243
+ text-align: center;
2244
+ color: #9ca3af;
2245
+ font-size: 11px;
2246
+ }
2247
+
2248
+ .ds-theme-dark .ds-table-sno {
2249
+ color: #64748b;
2250
+ }
2251
+
2252
+ .ds-table th {
2253
+ position: sticky;
2254
+ top: 0;
2255
+ padding: 8px 12px;
2256
+ text-align: left;
2257
+ font-weight: 600;
2258
+ font-size: 11px;
2259
+ text-transform: uppercase;
2260
+ letter-spacing: 0.05em;
2261
+ background: #f9fafb;
2262
+ color: #6b7280;
2263
+ border-bottom: 1px solid #e5e7eb;
2264
+ white-space: nowrap;
2265
+ }
2266
+
2267
+ .ds-theme-dark .ds-table th {
2268
+ background: #0f172a;
2269
+ color: #94a3b8;
2270
+ border-bottom-color: #334155;
2271
+ }
2272
+
2273
+ .ds-table td {
2274
+ padding: 8px 12px;
2275
+ border-bottom: 1px solid #f3f4f6;
2276
+ color: #374151;
2277
+ max-width: 200px;
2278
+ overflow: hidden;
2279
+ text-overflow: ellipsis;
2280
+ white-space: nowrap;
2281
+ }
2282
+
2283
+ .ds-theme-dark .ds-table td {
2284
+ color: #cbd5e1;
2285
+ border-bottom-color: #1e293b;
2286
+ }
2287
+
2288
+ .ds-table tr:hover td {
2289
+ background: #f9fafb;
2290
+ }
2291
+
2292
+ .ds-theme-dark .ds-table tr:hover td {
2293
+ background: #0f172a;
2294
+ }
2295
+
2296
+ /* ── Footer: info + pagination ───────────────────────────────────────────────*/
2297
+ .ds-table-footer {
2298
+ display: flex;
2299
+ align-items: center;
2300
+ justify-content: space-between;
2301
+ padding: 8px 14px;
2302
+ border-top: 1px solid #f3f4f6;
2303
+ flex-shrink: 0;
2304
+ gap: 8px;
2305
+ flex-wrap: wrap;
2306
+ }
2307
+
2308
+ .ds-theme-dark .ds-table-footer {
2309
+ border-top-color: #334155;
2310
+ }
2311
+
2312
+ .ds-table-info {
2313
+ display: flex;
2314
+ align-items: center;
2315
+ gap: 8px;
2316
+ font-size: 12px;
2317
+ color: #6b7280;
2318
+ }
2319
+
2320
+ .ds-theme-dark .ds-table-info {
2321
+ color: #94a3b8;
2322
+ }
2323
+
2324
+ .ds-table-rows-label {
2325
+ margin-left: 4px;
2326
+ }
2327
+
2328
+ .ds-table-page-size {
2329
+ height: 28px;
2330
+ padding: 0 6px;
2331
+ border: 1px solid #e5e7eb;
2332
+ border-radius: 5px;
2333
+ font-size: 12px;
2334
+ background: #fff;
2335
+ color: #374151;
2336
+ cursor: pointer;
2337
+ outline: none;
2338
+ }
2339
+
2340
+ .ds-theme-dark .ds-table-page-size {
2341
+ background: #0f172a;
2342
+ border-color: #334155;
2343
+ color: #e2e8f0;
2344
+ }
2345
+
2346
+ .ds-table-pagination {
2347
+ display: flex;
2348
+ align-items: center;
2349
+ gap: 6px;
2350
+ }
2351
+
2352
+ .ds-table-pg-btn {
2353
+ height: 28px;
2354
+ padding: 0 10px;
2355
+ border: 1px solid #e5e7eb;
2356
+ border-radius: 5px;
2357
+ font-size: 12px;
2358
+ font-weight: 500;
2359
+ background: #fff;
2360
+ color: #374151;
2361
+ cursor: pointer;
2362
+ transition: background 0.12s, border-color 0.12s;
2363
+ white-space: nowrap;
2364
+ }
2365
+
2366
+ .ds-table-pg-btn:hover:not(:disabled) {
2367
+ background: #f3f4f6;
2368
+ border-color: #d1d5db;
2369
+ }
2370
+
2371
+ .ds-table-pg-btn:disabled {
2372
+ opacity: 0.4;
2373
+ cursor: not-allowed;
2374
+ }
2375
+
2376
+ .ds-theme-dark .ds-table-pg-btn {
2377
+ background: #1e293b;
2378
+ border-color: #334155;
2379
+ color: #e2e8f0;
2380
+ }
2381
+
2382
+ .ds-theme-dark .ds-table-pg-btn:hover:not(:disabled) {
2383
+ background: #0f172a;
2384
+ }
2385
+
2386
+ .ds-table-pg-info {
2387
+ font-size: 12px;
2388
+ color: #6b7280;
2389
+ white-space: nowrap;
2390
+ }
2391
+
2392
+ .ds-theme-dark .ds-table-pg-info {
2393
+ color: #94a3b8;
2394
+ }
2395
+
2396
+ /* ── Loading overlay while fetching a page ───────────────────────────────────*/
2397
+ .ds-table-loading-overlay {
2398
+ position: absolute;
2399
+ inset: 0;
2400
+ background: rgba(255,255,255,0.6);
2401
+ display: flex;
2402
+ align-items: center;
2403
+ justify-content: center;
2404
+ border-radius: 10px;
2405
+ z-index: 10;
2406
+ }
2407
+
2408
+ .ds-theme-dark .ds-table-loading-overlay {
2409
+ background: rgba(30,41,59,0.7);
2410
+ }
2411
+
2412
+ /* ── KPI Number Card ───────────────────────────────────────────────────────── */
2413
+ .ds-card {
2414
+ width: 100%;
2415
+ height: 100%;
2416
+ border: 1px solid #e5e7eb;
2417
+ border-radius: 10px;
2418
+ display: flex;
2419
+ flex-direction: column;
2420
+ align-items: center;
2421
+ justify-content: center;
2422
+ padding: 16px;
2423
+ text-align: center;
2424
+ background: #ffffff;
2425
+ }
2426
+
2427
+ .ds-theme-dark .ds-card {
2428
+ background: #1e293b;
2429
+ border-color: #334155;
2430
+ }
2431
+
2432
+ .ds-card-label {
2433
+ font-size: 12px;
2434
+ font-weight: 500;
2435
+ color: #6b7280;
2436
+ margin-bottom: 6px;
2437
+ text-transform: uppercase;
2438
+ letter-spacing: 0.05em;
2439
+ }
2440
+
2441
+ .ds-card-value {
2442
+ font-size: 32px;
2443
+ font-weight: 700;
2444
+ color: #111827;
2445
+ line-height: 1.1;
2446
+ }
2447
+
2448
+ .ds-theme-dark .ds-card-value {
2449
+ color: #f1f5f9;
2450
+ }
2451
+
2452
+ /* ── v2: Text / content components ────────────────────────────────────────── */
2453
+
2454
+ .ds-text-heading {
2455
+ width: 100%;
2456
+ height: 100%;
2457
+ display: flex;
2458
+ flex-direction: column;
2459
+ justify-content: center;
2460
+ padding: 8px 12px;
2461
+ font-size: 20px;
2462
+ font-weight: 700;
2463
+ color: #111827;
2464
+ line-height: 1.3;
2465
+ overflow: hidden;
2466
+ }
2467
+
2468
+ .ds-theme-dark .ds-text-heading {
2469
+ color: #f1f5f9;
2470
+ }
2471
+
2472
+ .ds-text-subheading {
2473
+ width: 100%;
2474
+ height: 100%;
2475
+ display: flex;
2476
+ flex-direction: column;
2477
+ justify-content: center;
2478
+ padding: 6px 12px;
2479
+ font-size: 15px;
2480
+ font-weight: 600;
2481
+ color: #374151;
2482
+ line-height: 1.4;
2483
+ overflow: hidden;
2484
+ }
2485
+
2486
+ .ds-theme-dark .ds-text-subheading {
2487
+ color: #cbd5e1;
2488
+ }
2489
+
2490
+ .ds-text-body {
2491
+ width: 100%;
2492
+ height: 100%;
2493
+ padding: 8px 12px;
2494
+ font-size: 14px;
2495
+ color: #374151;
2496
+ line-height: 1.6;
2497
+ overflow: auto;
2498
+ }
2499
+
2500
+ .ds-theme-dark .ds-text-body {
2501
+ color: #94a3b8;
2502
+ }
2503
+
2504
+ .ds-text-box {
2505
+ width: 100%;
2506
+ height: 100%;
2507
+ padding: 10px 14px;
2508
+ font-size: 13px;
2509
+ color: #374151;
2510
+ line-height: 1.6;
2511
+ border: 1px solid #e5e7eb;
2512
+ border-radius: 8px;
2513
+ background: #f9fafb;
2514
+ overflow: auto;
2515
+ }
2516
+
2517
+ .ds-theme-dark .ds-text-box {
2518
+ color: #cbd5e1;
2519
+ border-color: #334155;
2520
+ background: #1e293b;
2521
+ }
2522
+
2523
+ /* ── v2: Header component ──────────────────────────────────────────────────── */
2524
+
2525
+ .ds-header-component {
2526
+ width: 100%;
2527
+ height: 100%;
2528
+ display: flex;
2529
+ align-items: center;
2530
+ padding: 12px 20px;
2531
+ background: #f3f4f6;
2532
+ border-bottom: 1px solid #e5e7eb;
2533
+ overflow: hidden;
2534
+ }
2535
+
2536
+ .ds-theme-dark .ds-header-component {
2537
+ background: #1e293b;
2538
+ border-bottom-color: #334155;
2539
+ }
2540
+
2541
+ .ds-header-content {
2542
+ font-size: 18px;
2543
+ font-weight: 700;
2544
+ color: #111827;
2545
+ white-space: nowrap;
2546
+ overflow: hidden;
2547
+ text-overflow: ellipsis;
2548
+ }
2549
+
2550
+ .ds-theme-dark .ds-header-content {
2551
+ color: #f1f5f9;
2552
+ }
2553
+
2554
+ /* ── v2: Section container ─────────────────────────────────────────────────── */
2555
+
2556
+ .ds-section-wrapper {
2557
+ width: 100%;
2558
+ height: 100%;
2559
+ border: 1px solid #e5e7eb;
2560
+ border-radius: 10px;
2561
+ background: #f9fafb;
2562
+ overflow: hidden;
2563
+ }
2564
+
2565
+ .ds-theme-dark .ds-section-wrapper {
2566
+ border-color: #334155;
2567
+ background: #1e293b;
2568
+ }
2569
+
2570
+ .ds-section-title {
2571
+ padding: 8px 14px;
2572
+ font-size: 12px;
2573
+ font-weight: 600;
2574
+ text-transform: uppercase;
2575
+ letter-spacing: 0.05em;
2576
+ color: #6b7280;
2577
+ border-bottom: 1px solid #e5e7eb;
2578
+ }
2579
+
2580
+ .ds-theme-dark .ds-section-title {
2581
+ color: #94a3b8;
2582
+ border-bottom-color: #334155;
2583
+ }
2584
+
2585
+ /* ── Filter panel (publish-page style: toggle + collapsible items + Apply) ─── */
2586
+
2587
+ .ds-fp {
2588
+ border-bottom: 1px solid #e5e7eb;
2589
+ background: inherit;
2590
+ }
2591
+
2592
+ .ds-theme-dark .ds-fp {
2593
+ border-bottom-color: #1e293b;
2594
+ }
2595
+
2596
+ /* ── Toggle bar ────────────────────────────────────────────────────────────── */
2597
+ .ds-fp-bar {
2598
+ display: flex;
2599
+ align-items: center;
2600
+ gap: 12px;
2601
+ padding: 8px 16px;
2602
+ }
2603
+
2604
+ /* ── Panel header ──────────────────────────────────────────────────────────── */
2605
+ .ds-fp-panel-hd {
2606
+ display: flex;
2607
+ align-items: center;
2608
+ justify-content: space-between;
2609
+ padding: 14px 16px 12px;
2610
+ border-bottom: 1px solid #e5e7eb;
2611
+ }
2612
+
2613
+ .ds-theme-dark .ds-fp-panel-hd {
2614
+ border-bottom-color: #334155;
2615
+ }
2616
+
2617
+ .ds-fp-panel-title {
2618
+ display: flex;
2619
+ align-items: center;
2620
+ gap: 8px;
2621
+ font-size: 14px;
2622
+ font-weight: 600;
2623
+ color: #111827;
2624
+ }
2625
+
2626
+ .ds-theme-dark .ds-fp-panel-title {
2627
+ color: #f1f5f9;
2628
+ }
2629
+
2630
+ .ds-fp-panel-title svg {
2631
+ width: 16px;
2632
+ height: 16px;
2633
+ color: #6b7280;
2634
+ flex-shrink: 0;
2635
+ }
2636
+
2637
+ .ds-fp-close-btn {
2638
+ width: 28px;
2639
+ height: 28px;
2640
+ display: flex;
2641
+ align-items: center;
2642
+ justify-content: center;
2643
+ border: 1px solid #e5e7eb;
2644
+ border-radius: 6px;
2645
+ background: transparent;
2646
+ color: #6b7280;
2647
+ cursor: pointer;
2648
+ padding: 0;
2649
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
2650
+ flex-shrink: 0;
2651
+ }
2652
+
2653
+ .ds-fp-close-btn svg {
2654
+ width: 14px;
2655
+ height: 14px;
2656
+ }
2657
+
2658
+ .ds-fp-close-btn:hover {
2659
+ border-color: #6366f1;
2660
+ color: #6366f1;
2661
+ background: #eef2ff;
2662
+ }
2663
+
2664
+ .ds-theme-dark .ds-fp-close-btn {
2665
+ border-color: #334155;
2666
+ color: #94a3b8;
2667
+ }
2668
+
2669
+ /* ── Reset All section (inside panel) ─────────────────────────────────────── */
2670
+ .ds-fp-reset-section {
2671
+ padding: 10px 16px;
2672
+ border-bottom: 1px solid #f3f4f6;
2673
+ }
2674
+
2675
+ .ds-theme-dark .ds-fp-reset-section {
2676
+ border-bottom-color: #1e293b;
2677
+ }
2678
+
2679
+ .ds-fp-reset-all {
2680
+ display: inline-flex;
2681
+ align-items: center;
2682
+ gap: 6px;
2683
+ border: none;
2684
+ background: transparent;
2685
+ color: #6366f1;
2686
+ font-size: 13px;
2687
+ font-weight: 500;
2688
+ cursor: pointer;
2689
+ padding: 0;
2690
+ transition: color 0.15s;
2691
+ }
2692
+
2693
+ .ds-fp-reset-all svg {
2694
+ width: 13px;
2695
+ height: 13px;
2696
+ }
2697
+
2698
+ .ds-fp-reset-all:hover {
2699
+ color: #4338ca;
2700
+ }
2701
+
2702
+ .ds-theme-dark .ds-fp-reset-all {
2703
+ color: #818cf8;
2704
+ }
2705
+
2706
+ .ds-fp-toggle {
2707
+ display: flex;
2708
+ align-items: center;
2709
+ gap: 6px;
2710
+ padding: 6px 12px;
2711
+ border: 1px solid #d1d5db;
2712
+ border-radius: 8px;
2713
+ background: #ffffff;
2714
+ color: #374151;
2715
+ font-size: 13px;
2716
+ font-weight: 500;
2717
+ cursor: pointer;
2718
+ transition: border-color 0.15s, background 0.15s;
2719
+ }
2720
+
2721
+ .ds-fp-toggle:hover {
2722
+ border-color: #6366f1;
2723
+ background: #f5f3ff;
2724
+ }
2725
+
2726
+ .ds-theme-dark .ds-fp-toggle {
2727
+ background: #1e293b;
2728
+ border-color: #334155;
2729
+ color: #e2e8f0;
2730
+ }
2731
+
2732
+ .ds-fp-funnel {
2733
+ width: 13px;
2734
+ height: 13px;
2735
+ flex-shrink: 0;
2736
+ }
2737
+
2738
+ .ds-fp-badge {
2739
+ display: inline-flex;
2740
+ align-items: center;
2741
+ justify-content: center;
2742
+ min-width: 18px;
2743
+ height: 18px;
2744
+ padding: 0 5px;
2745
+ border-radius: 9px;
2746
+ background: #6366f1;
2747
+ color: #fff;
2748
+ font-size: 11px;
2749
+ font-weight: 700;
2750
+ line-height: 1;
2751
+ }
2752
+
2753
+ .ds-fp-reset-all {
2754
+ border: none;
2755
+ background: transparent;
2756
+ color: #6366f1;
2757
+ font-size: 12px;
2758
+ font-weight: 500;
2759
+ cursor: pointer;
2760
+ padding: 0;
2761
+ text-decoration: underline;
2762
+ transition: color 0.15s;
2763
+ }
2764
+
2765
+ .ds-fp-reset-all:hover {
2766
+ color: #4338ca;
2767
+ }
2768
+
2769
+ .ds-theme-dark .ds-fp-reset-all {
2770
+ color: #818cf8;
2771
+ }
2772
+
2773
+ /* ── Expandable panel ──────────────────────────────────────────────────────── */
2774
+ .ds-fp-panel {
2775
+ padding: 0 16px 10px;
2776
+ }
2777
+
2778
+ .ds-fp-items {
2779
+ display: flex;
2780
+ flex-direction: column;
2781
+ gap: 4px;
2782
+ }
2783
+
2784
+ /* ── Filter item ───────────────────────────────────────────────────────────── */
2785
+ .ds-fp-item {
2786
+ border: 1px solid #e5e7eb;
2787
+ border-radius: 8px;
2788
+ overflow: hidden;
2789
+ background: #fafafa;
2790
+ }
2791
+
2792
+ .ds-theme-dark .ds-fp-item {
2793
+ border-color: #334155;
2794
+ background: #1e293b;
2795
+ }
2796
+
2797
+ .ds-fp-item-hd {
2798
+ display: flex;
2799
+ align-items: center;
2800
+ gap: 8px;
2801
+ padding: 9px 12px;
2802
+ cursor: pointer;
2803
+ user-select: none;
2804
+ transition: background 0.12s;
2805
+ }
2806
+
2807
+ .ds-fp-item-hd:hover {
2808
+ background: #f0f0ff;
2809
+ }
2810
+
2811
+ .ds-theme-dark .ds-fp-item-hd:hover {
2812
+ background: #0f172a;
2813
+ }
2814
+
2815
+ .ds-fp-item-name {
2816
+ flex: 1;
2817
+ font-size: 13px;
2818
+ font-weight: 600;
2819
+ color: #374151;
2820
+ }
2821
+
2822
+ .ds-theme-dark .ds-fp-item-name {
2823
+ color: #e2e8f0;
2824
+ }
2825
+
2826
+ .ds-fp-val-badge {
2827
+ font-size: 11px;
2828
+ font-weight: 600;
2829
+ padding: 2px 7px;
2830
+ border-radius: 10px;
2831
+ background: #e0e7ff;
2832
+ color: #4338ca;
2833
+ white-space: nowrap;
2834
+ max-width: 120px;
2835
+ overflow: hidden;
2836
+ text-overflow: ellipsis;
2837
+ }
2838
+
2839
+ .ds-theme-dark .ds-fp-val-badge {
2840
+ background: #312e81;
2841
+ color: #a5b4fc;
2842
+ }
2843
+
2844
+ .ds-fp-item-clear {
2845
+ border: none;
2846
+ background: transparent;
2847
+ color: #9ca3af;
2848
+ cursor: pointer;
2849
+ padding: 2px;
2850
+ line-height: 1;
2851
+ flex-shrink: 0;
2852
+ transition: color 0.12s;
2853
+ display: flex;
2854
+ align-items: center;
2855
+ }
2856
+
2857
+ .ds-fp-item-clear svg {
2858
+ width: 13px;
2859
+ height: 13px;
2860
+ }
2861
+
2862
+ .ds-fp-item-clear:hover {
2863
+ color: #6366f1;
2864
+ }
2865
+
2866
+ .ds-fp-chevron {
2867
+ width: 14px;
2868
+ height: 14px;
2869
+ color: #9ca3af;
2870
+ flex-shrink: 0;
2871
+ transition: transform 0.2s ease;
2872
+ }
2873
+
2874
+ /* Indigo border + highlight when expanded */
2875
+ .ds-fp-item-expanded,
2876
+ .ds-fp-item:has(.ds-fp-item-body) {
2877
+ border-color: #6366f1;
2878
+ background: #fafafe;
2879
+ }
2880
+
2881
+ .ds-theme-dark .ds-fp-item-expanded,
2882
+ .ds-theme-dark .ds-fp-item:has(.ds-fp-item-body) {
2883
+ border-color: #6366f1;
2884
+ background: #1e1e3a;
2885
+ }
2886
+
2887
+ /* ── Item body (expanded content) ──────────────────────────────────────────── */
2888
+ .ds-fp-item-body {
2889
+ padding: 4px 12px 12px;
2890
+ display: flex;
2891
+ flex-direction: column;
2892
+ gap: 8px;
2893
+ border-top: 1px solid #e5e7eb;
2894
+ }
2895
+
2896
+ .ds-theme-dark .ds-fp-item-body {
2897
+ border-top-color: #334155;
2898
+ }
2899
+
2900
+ /* ── Operator row ──────────────────────────────────────────────────────────── */
2901
+ .ds-fp-op-row {
2902
+ display: flex;
2903
+ align-items: center;
2904
+ gap: 8px;
2905
+ }
2906
+
2907
+ .ds-fp-op-lbl {
2908
+ font-size: 11px;
2909
+ font-weight: 600;
2910
+ text-transform: uppercase;
2911
+ letter-spacing: 0.04em;
2912
+ color: #9ca3af;
2913
+ white-space: nowrap;
2914
+ flex-shrink: 0;
2915
+ }
2916
+
2917
+ .ds-fp-op-select {
2918
+ flex: 1;
2919
+ height: 30px;
2920
+ padding: 0 8px;
2921
+ border: 1px solid #d1d5db;
2922
+ border-radius: 6px;
2923
+ font-size: 12px;
2924
+ background: #fff;
2925
+ color: #374151;
2926
+ cursor: pointer;
2927
+ outline: none;
2928
+ transition: border-color 0.15s;
2929
+ }
2930
+
2931
+ .ds-fp-op-select:focus {
2932
+ border-color: #6366f1;
2933
+ box-shadow: 0 0 0 2px rgba(99,102,241,0.15);
2934
+ }
2935
+
2936
+ .ds-theme-dark .ds-fp-op-select {
2937
+ background: #0f172a;
2938
+ border-color: #334155;
2939
+ color: #e2e8f0;
2940
+ }
2941
+
2942
+ /* ── Search wrap (input + caret button) ────────────────────────────────────── */
2943
+ .ds-fp-search-wrap {
2944
+ display: flex;
2945
+ align-items: center;
2946
+ border: 1px solid #6366f1;
2947
+ border-radius: 8px;
2948
+ background: #fff;
2949
+ overflow: hidden;
2950
+ transition: box-shadow 0.15s;
2951
+ }
2952
+
2953
+ .ds-fp-search-wrap:focus-within {
2954
+ box-shadow: 0 0 0 3px rgba(99,102,241,0.15);
2955
+ }
2956
+
2957
+ .ds-theme-dark .ds-fp-search-wrap {
2958
+ background: #0f172a;
2959
+ border-color: #6366f1;
2960
+ }
2961
+
2962
+ .ds-fp-ms-search {
2963
+ flex: 1;
2964
+ height: 36px;
2965
+ padding: 0 10px;
2966
+ border: none;
2967
+ outline: none;
2968
+ font-size: 13px;
2969
+ background: transparent;
2970
+ color: #374151;
2971
+ }
2972
+
2973
+ .ds-theme-dark .ds-fp-ms-search {
2974
+ color: #e2e8f0;
2975
+ }
2976
+
2977
+ .ds-fp-ms-search::placeholder {
2978
+ color: #9ca3af;
2979
+ }
2980
+
2981
+ .ds-fp-search-caret {
2982
+ width: 32px;
2983
+ height: 36px;
2984
+ display: flex;
2985
+ align-items: center;
2986
+ justify-content: center;
2987
+ border: none;
2988
+ border-left: 1px solid #e5e7eb;
2989
+ background: transparent;
2990
+ color: #6b7280;
2991
+ cursor: pointer;
2992
+ padding: 0;
2993
+ flex-shrink: 0;
2994
+ transition: color 0.12s, background 0.12s;
2995
+ }
2996
+
2997
+ .ds-fp-search-caret:hover {
2998
+ color: #6366f1;
2999
+ background: #eef2ff;
3000
+ }
3001
+
3002
+ .ds-theme-dark .ds-fp-search-caret {
3003
+ border-left-color: #334155;
3004
+ }
3005
+
3006
+ /* ── Multi-select container ────────────────────────────────────────────────── */
3007
+ .ds-fp-ms {
3008
+ border: 1px solid #e5e7eb;
3009
+ border-radius: 8px;
3010
+ overflow: hidden;
3011
+ }
3012
+
3013
+ .ds-theme-dark .ds-fp-ms {
3014
+ border-color: #334155;
3015
+ }
3016
+
3017
+ /* Chips row (shown above search when values selected) */
3018
+ .ds-fp-chips-row {
3019
+ display: flex;
3020
+ flex-wrap: wrap;
3021
+ align-items: center;
3022
+ gap: 4px;
3023
+ padding: 6px 0 4px;
3024
+ }
3025
+
3026
+ .ds-fp-chip {
3027
+ display: inline-flex;
3028
+ align-items: center;
3029
+ gap: 4px;
3030
+ padding: 2px 8px 2px 8px;
3031
+ border-radius: 12px;
3032
+ background: #e0e7ff;
3033
+ color: #4338ca;
3034
+ font-size: 11px;
3035
+ font-weight: 600;
3036
+ white-space: nowrap;
3037
+ }
3038
+
3039
+ .ds-theme-dark .ds-fp-chip {
3040
+ background: #312e81;
3041
+ color: #a5b4fc;
3042
+ }
3043
+
3044
+ .ds-fp-chip-x {
3045
+ border: none;
3046
+ background: transparent;
3047
+ color: inherit;
3048
+ font-size: 10px;
3049
+ cursor: pointer;
3050
+ padding: 0;
3051
+ line-height: 1;
3052
+ opacity: 0.7;
3053
+ transition: opacity 0.1s;
3054
+ }
3055
+
3056
+ .ds-fp-chip-x:hover {
3057
+ opacity: 1;
3058
+ }
3059
+
3060
+ /* ds-fp-ms-search styles live in .ds-fp-search-wrap above */
3061
+
3062
+ /* Options list */
3063
+ .ds-fp-opts {
3064
+ max-height: 180px;
3065
+ overflow-y: auto;
3066
+ }
3067
+
3068
+ .ds-fp-opt {
3069
+ display: flex;
3070
+ align-items: center;
3071
+ gap: 8px;
3072
+ padding: 7px 10px;
3073
+ cursor: pointer;
3074
+ transition: background 0.1s;
3075
+ user-select: none;
3076
+ }
3077
+
3078
+ .ds-fp-opt:hover {
3079
+ background: #f0f0ff;
3080
+ }
3081
+
3082
+ .ds-theme-dark .ds-fp-opt:hover {
3083
+ background: #1e293b;
3084
+ }
3085
+
3086
+ .ds-fp-opt-on {
3087
+ background: #eef2ff;
3088
+ }
3089
+
3090
+ .ds-theme-dark .ds-fp-opt-on {
3091
+ background: #1e293b;
3092
+ }
3093
+
3094
+ /* Custom checkbox */
3095
+ .ds-fp-cb {
3096
+ width: 16px;
3097
+ height: 16px;
3098
+ border: 2px solid #d1d5db;
3099
+ border-radius: 4px;
3100
+ flex-shrink: 0;
3101
+ display: flex;
3102
+ align-items: center;
3103
+ justify-content: center;
3104
+ transition: border-color 0.1s, background 0.1s;
3105
+ }
3106
+
3107
+ .ds-fp-cb-on {
3108
+ border-color: #6366f1;
3109
+ background: #6366f1;
3110
+ }
3111
+
3112
+ .ds-fp-cb-on svg {
3113
+ width: 10px;
3114
+ height: 10px;
3115
+ stroke: #fff;
3116
+ }
3117
+
3118
+ .ds-fp-opt-lbl {
3119
+ font-size: 12px;
3120
+ color: #374151;
3121
+ flex: 1;
3122
+ overflow: hidden;
3123
+ text-overflow: ellipsis;
3124
+ white-space: nowrap;
3125
+ }
3126
+
3127
+ .ds-theme-dark .ds-fp-opt-lbl {
3128
+ color: #cbd5e1;
3129
+ }
3130
+
3131
+ .ds-fp-opts-msg {
3132
+ display: flex;
3133
+ align-items: center;
3134
+ justify-content: center;
3135
+ gap: 8px;
3136
+ padding: 14px 12px;
3137
+ font-size: 12px;
3138
+ color: #9ca3af;
3139
+ }
3140
+
3141
+ /* ── Apply Filter button ───────────────────────────────────────────────────── */
3142
+ .ds-fp-apply {
3143
+ width: 100%;
3144
+ padding: 9px 16px;
3145
+ border: none;
3146
+ border-radius: 8px;
3147
+ background: linear-gradient(135deg, #16a34a, #15803d);
3148
+ color: #fff;
3149
+ font-size: 13px;
3150
+ font-weight: 600;
3151
+ cursor: pointer;
3152
+ transition: opacity 0.15s, transform 0.1s;
3153
+ letter-spacing: 0.01em;
3154
+ }
3155
+
3156
+ .ds-fp-apply:hover {
3157
+ opacity: 0.9;
3158
+ }
3159
+
3160
+ .ds-fp-apply:active {
3161
+ transform: scale(0.98);
3162
+ }
3163
+
3164
+ /* ── Text / date inputs inside filter body ─────────────────────────────────── */
3165
+ .ds-fp-text-val {
3166
+ width: 100%;
3167
+ height: 34px;
3168
+ padding: 0 10px;
3169
+ border: 1px solid #d1d5db;
3170
+ border-radius: 6px;
3171
+ font-size: 13px;
3172
+ background: #fff;
3173
+ color: #374151;
3174
+ outline: none;
3175
+ transition: border-color 0.15s;
3176
+ }
3177
+
3178
+ .ds-fp-text-val:focus {
3179
+ border-color: #6366f1;
3180
+ box-shadow: 0 0 0 2px rgba(99,102,241,0.15);
3181
+ }
3182
+
3183
+ .ds-theme-dark .ds-fp-text-val {
3184
+ background: #0f172a;
3185
+ border-color: #334155;
3186
+ color: #e2e8f0;
3187
+ }
3188
+
3189
+ .ds-fp-date-range {
3190
+ display: flex;
3191
+ align-items: center;
3192
+ gap: 6px;
3193
+ }
3194
+
3195
+ .ds-fp-date {
3196
+ flex: 1;
3197
+ height: 34px;
3198
+ padding: 0 8px;
3199
+ border: 1px solid #d1d5db;
3200
+ border-radius: 6px;
3201
+ font-size: 12px;
3202
+ background: #fff;
3203
+ color: #374151;
3204
+ outline: none;
3205
+ transition: border-color 0.15s;
3206
+ }
3207
+
3208
+ .ds-fp-date:focus {
3209
+ border-color: #6366f1;
3210
+ }
3211
+
3212
+ .ds-fp-date-sep {
3213
+ color: #9ca3af;
3214
+ font-size: 12px;
3215
+ }
3216
+
3217
+ .ds-theme-dark .ds-fp-date {
3218
+ background: #0f172a;
3219
+ border-color: #334155;
3220
+ color: #e2e8f0;
3221
+ }
3222
+
3223
+ /* ── Component-level inline filter button (funnel icon on chart card) ──────── */
3224
+ .ds-comp-filter-btn {
3225
+ position: absolute;
3226
+ top: 6px;
3227
+ right: 6px;
3228
+ z-index: 10;
3229
+ width: 28px;
3230
+ height: 28px;
3231
+ display: flex;
3232
+ align-items: center;
3233
+ justify-content: center;
3234
+ border: 1px solid #e5e7eb;
3235
+ border-radius: 6px;
3236
+ background: rgba(255,255,255,0.9);
3237
+ color: #6b7280;
3238
+ cursor: pointer;
3239
+ padding: 0;
3240
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
3241
+ backdrop-filter: blur(4px);
3242
+ }
3243
+
3244
+ .ds-comp-filter-btn:hover {
3245
+ border-color: #6366f1;
3246
+ color: #6366f1;
3247
+ background: #fff;
3248
+ }
3249
+
3250
+ .ds-comp-filter-btn-active {
3251
+ border-color: #6366f1;
3252
+ color: #6366f1;
3253
+ background: #eef2ff;
3254
+ }
3255
+
3256
+ .ds-comp-filter-btn svg {
3257
+ width: 13px;
3258
+ height: 13px;
3259
+ }
3260
+
3261
+ .ds-theme-dark .ds-comp-filter-btn {
3262
+ background: rgba(15,23,42,0.85);
3263
+ border-color: #334155;
3264
+ color: #94a3b8;
3265
+ }
3266
+
3267
+ .ds-theme-dark .ds-comp-filter-btn-active {
3268
+ border-color: #6366f1;
3269
+ color: #818cf8;
3270
+ background: #1e293b;
3271
+ }
3272
+
3273
+ /* ── Inline filter popover ─────────────────────────────────────────────────── */
3274
+ .ds-comp-filter-pop {
3275
+ position: absolute;
3276
+ top: 38px;
3277
+ right: 6px;
3278
+ z-index: 9999;
3279
+ }
3280
+
3281
+ /* Visual shell for the inline popover — same look as global filter panel */
3282
+ .ds-fp-panel-pop {
3283
+ width: 300px;
3284
+ max-height: 440px;
3285
+ overflow-y: auto;
3286
+ background: #fff;
3287
+ border: 1px solid #e5e7eb;
3288
+ border-radius: 10px;
3289
+ box-shadow: 0 8px 24px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.06);
3290
+ }
3291
+
3292
+ .ds-theme-dark .ds-fp-panel-pop {
3293
+ background: #1e293b;
3294
+ border-color: #334155;
3295
+ box-shadow: 0 8px 24px rgba(0,0,0,0.4);
3296
+ }
3297
+
3298
+ .ds-cpop-inner {
3299
+ display: flex;
3300
+ flex-direction: column;
3301
+ gap: 6px;
3302
+ padding: 10px;
3303
+ }
3304
+
3305
+ .ds-cpop-title {
3306
+ font-size: 11px;
3307
+ font-weight: 700;
3308
+ text-transform: uppercase;
3309
+ letter-spacing: 0.05em;
3310
+ color: #6b7280;
3311
+ padding-bottom: 4px;
3312
+ border-bottom: 1px solid #f3f4f6;
3313
+ }
3314
+
3315
+ .ds-theme-dark .ds-cpop-title {
3316
+ color: #94a3b8;
3317
+ border-bottom-color: #334155;
3318
+ }
3319
+
3320
+ .ds-cpop-label {
3321
+ font-size: 12px;
3322
+ font-weight: 600;
3323
+ color: #374151;
3324
+ margin-bottom: 4px;
3325
+ }
3326
+
3327
+ .ds-theme-dark .ds-cpop-label {
3328
+ color: #e2e8f0;
3329
+ }
3330
+
3331
+ .ds-cpop-opts-wrap {
3332
+ border: 1px solid #e5e7eb;
3333
+ border-radius: 6px;
3334
+ overflow: hidden;
3335
+ margin-bottom: 6px;
3336
+ }
3337
+
3338
+ .ds-theme-dark .ds-cpop-opts-wrap {
3339
+ border-color: #334155;
3340
+ }
3341
+
3342
+ .ds-cpop-opts {
3343
+ max-height: 160px;
3344
+ overflow-y: auto;
3345
+ }
3346
+
3347
+ .ds-cpop-opt {
3348
+ display: flex;
3349
+ align-items: center;
3350
+ gap: 8px;
3351
+ padding: 6px 8px;
3352
+ cursor: pointer;
3353
+ font-size: 12px;
3354
+ color: #374151;
3355
+ transition: background 0.1s;
3356
+ user-select: none;
3357
+ }
3358
+
3359
+ .ds-cpop-opt:hover {
3360
+ background: #f0f0ff;
3361
+ }
3362
+
3363
+ .ds-theme-dark .ds-cpop-opt {
3364
+ color: #cbd5e1;
3365
+ }
3366
+
3367
+ .ds-theme-dark .ds-cpop-opt:hover {
3368
+ background: #0f172a;
3369
+ }
3370
+
3371
+ .ds-cpop-apply {
3372
+ font-size: 12px;
3373
+ padding: 7px 12px;
3374
+ }
3375
+
3376
+ /* ── Custom dropdown filter (matches publish page SelectFilter) ────────────── */
3377
+
3378
+ .ds-custom-dd {
3379
+ position: relative;
3380
+ min-width: 150px;
3381
+ }
3382
+
3383
+ .ds-dd-trigger {
3384
+ width: 100%;
3385
+ height: 34px;
3386
+ padding: 0 10px;
3387
+ display: flex;
3388
+ align-items: center;
3389
+ justify-content: space-between;
3390
+ gap: 6px;
3391
+ background: #ffffff;
3392
+ border: 1px solid #d1d5db;
3393
+ border-radius: 8px;
3394
+ font-size: 13px;
3395
+ color: #111827;
3396
+ cursor: pointer;
3397
+ text-align: left;
3398
+ transition: border-color 0.15s, box-shadow 0.15s;
3399
+ white-space: nowrap;
3400
+ }
3401
+
3402
+ .ds-dd-trigger:hover {
3403
+ border-color: #6366f1;
3404
+ }
3405
+
3406
+ .ds-dd-trigger:focus {
3407
+ outline: none;
3408
+ border-color: #6366f1;
3409
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
3410
+ }
3411
+
3412
+ .ds-theme-dark .ds-dd-trigger {
3413
+ background: #1e293b;
3414
+ border-color: #334155;
3415
+ color: #f1f5f9;
3416
+ }
3417
+
3418
+ .ds-dd-value {
3419
+ flex: 1;
3420
+ overflow: hidden;
3421
+ text-overflow: ellipsis;
3422
+ color: inherit;
3423
+ }
3424
+
3425
+ /* "All" placeholder style */
3426
+ .ds-dd-trigger:not(.ds-dd-has-value) .ds-dd-value {
3427
+ color: #9ca3af;
3428
+ }
3429
+
3430
+ .ds-dd-actions {
3431
+ display: flex;
3432
+ align-items: center;
3433
+ gap: 4px;
3434
+ flex-shrink: 0;
3435
+ }
3436
+
3437
+ .ds-dd-clear {
3438
+ font-size: 11px;
3439
+ color: #9ca3af;
3440
+ cursor: pointer;
3441
+ padding: 0 2px;
3442
+ line-height: 1;
3443
+ }
3444
+
3445
+ .ds-dd-clear:hover {
3446
+ color: #374151;
3447
+ }
3448
+
3449
+ .ds-theme-dark .ds-dd-clear:hover {
3450
+ color: #e2e8f0;
3451
+ }
3452
+
3453
+ .ds-dd-arrow {
3454
+ color: #9ca3af;
3455
+ transition: transform 0.18s ease;
3456
+ display: block;
3457
+ }
3458
+
3459
+ /* ── Dropdown panel ────────────────────────────────────────────────────────── */
3460
+
3461
+ .ds-dd-panel {
3462
+ position: absolute;
3463
+ top: calc(100% + 4px);
3464
+ left: 0;
3465
+ right: 0;
3466
+ min-width: 180px;
3467
+ background: #ffffff;
3468
+ border: 1px solid #e5e7eb;
3469
+ border-radius: 10px;
3470
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.06);
3471
+ z-index: 9999;
3472
+ overflow: hidden;
3473
+ }
3474
+
3475
+ .ds-theme-dark .ds-dd-panel {
3476
+ background: #1e293b;
3477
+ border-color: #334155;
3478
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
3479
+ }
3480
+
3481
+ /* Search row */
3482
+ .ds-dd-search-wrap {
3483
+ padding: 8px 8px 6px;
3484
+ border-bottom: 1px solid #f3f4f6;
3485
+ }
3486
+
3487
+ .ds-theme-dark .ds-dd-search-wrap {
3488
+ border-bottom-color: #334155;
3489
+ }
3490
+
3491
+ .ds-dd-search {
3492
+ width: 100%;
3493
+ height: 30px;
3494
+ padding: 0 10px;
3495
+ border: 1px solid #e5e7eb;
3496
+ border-radius: 6px;
3497
+ font-size: 12px;
3498
+ background: #f9fafb;
3499
+ color: #374151;
3500
+ outline: none;
3501
+ transition: border-color 0.15s, box-shadow 0.15s;
3502
+ }
3503
+
3504
+ .ds-dd-search:focus {
3505
+ border-color: #6366f1;
3506
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
3507
+ background: #fff;
3508
+ }
3509
+
3510
+ .ds-theme-dark .ds-dd-search {
3511
+ background: #0f172a;
3512
+ border-color: #334155;
3513
+ color: #e2e8f0;
3514
+ }
3515
+
3516
+ .ds-theme-dark .ds-dd-search:focus {
3517
+ background: #1e293b;
3518
+ }
3519
+
3520
+ /* Options list */
3521
+ .ds-dd-options {
3522
+ max-height: 200px;
3523
+ overflow-y: auto;
3524
+ }
3525
+
3526
+ .ds-dd-opt {
3527
+ width: 100%;
3528
+ padding: 8px 12px;
3529
+ display: flex;
3530
+ align-items: center;
3531
+ justify-content: space-between;
3532
+ gap: 8px;
3533
+ background: transparent;
3534
+ border: none;
3535
+ font-size: 13px;
3536
+ color: #374151;
3537
+ cursor: pointer;
3538
+ text-align: left;
3539
+ transition: background 0.1s;
3540
+ }
3541
+
3542
+ .ds-dd-opt:hover {
3543
+ background: #eef2ff;
3544
+ }
3545
+
3546
+ .ds-dd-opt-active {
3547
+ background: #eef2ff;
3548
+ color: #4338ca;
3549
+ font-weight: 600;
3550
+ }
3551
+
3552
+ .ds-dd-opt-active:hover {
3553
+ background: #e0e7ff;
3554
+ }
3555
+
3556
+ .ds-theme-dark .ds-dd-opt {
3557
+ color: #cbd5e1;
3558
+ }
3559
+
3560
+ .ds-theme-dark .ds-dd-opt:hover {
3561
+ background: #0f172a;
3562
+ }
3563
+
3564
+ .ds-theme-dark .ds-dd-opt-active {
3565
+ background: #1e293b;
3566
+ color: #818cf8;
3567
+ }
3568
+
3569
+ .ds-dd-opt-text {
3570
+ flex: 1;
3571
+ overflow: hidden;
3572
+ text-overflow: ellipsis;
3573
+ white-space: nowrap;
3574
+ }
3575
+
3576
+ .ds-dd-check {
3577
+ color: #4338ca;
3578
+ flex-shrink: 0;
3579
+ }
3580
+
3581
+ .ds-theme-dark .ds-dd-check {
3582
+ color: #818cf8;
3583
+ }
3584
+
3585
+ /* Empty / loading message */
3586
+ .ds-dd-msg {
3587
+ display: flex;
3588
+ align-items: center;
3589
+ justify-content: center;
3590
+ gap: 8px;
3591
+ padding: 14px 12px;
3592
+ font-size: 12px;
3593
+ color: #9ca3af;
3594
+ }
3595
+
3596
+ .ds-theme-dark .ds-dd-msg {
3597
+ color: #64748b;
3598
+ }
3599
+
3600
+ /* Spinner inside dropdown */
3601
+ .ds-dd-spinner {
3602
+ width: 14px;
3603
+ height: 14px;
3604
+ border: 2px solid #e5e7eb;
3605
+ border-top-color: #6366f1;
3606
+ border-radius: 50%;
3607
+ animation: ds-spin 0.8s linear infinite;
3608
+ flex-shrink: 0;
3609
+ }
3610
+
3611
+ /* ── v2: Inline filter (selection-filter placed in-grid) ───────────────────── */
3612
+
3613
+ .ds-inline-filter {
3614
+ width: 100%;
3615
+ height: 100%;
3616
+ display: flex;
3617
+ flex-direction: column;
3618
+ justify-content: center;
3619
+ gap: 4px;
3620
+ padding: 6px 10px;
3621
+ }
3622
+
3623
+ .ds-inline-filter-select,
3624
+ .ds-inline-filter-input {
3625
+ width: 100%;
3626
+ height: 34px;
3627
+ padding: 0 10px;
3628
+ border: 1px solid #d1d5db;
3629
+ border-radius: 6px;
3630
+ font-size: 13px;
3631
+ background: #ffffff;
3632
+ color: #111827;
3633
+ cursor: pointer;
3634
+ outline: none;
3635
+ transition: border-color 0.15s, box-shadow 0.15s;
3636
+ }
3637
+
3638
+ .ds-inline-filter-select:focus,
3639
+ .ds-inline-filter-input:focus {
3640
+ border-color: #6366f1;
3641
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
3642
+ }
3643
+
3644
+ .ds-theme-dark .ds-inline-filter-select,
3645
+ .ds-theme-dark .ds-inline-filter-input {
3646
+ background: #1e293b;
3647
+ border-color: #334155;
3648
+ color: #f1f5f9;
3649
+ }
3650
+
3651
+ /* ── Export toolbar ────────────────────────────────────────────────────────── */
3652
+ .ds-toolbar {
3653
+ display: flex;
3654
+ justify-content: flex-end;
3655
+ padding: 8px 16px 0;
3656
+ gap: 8px;
3657
+ }
3658
+
3659
+ .ds-toolbar-btn {
3660
+ height: 30px;
3661
+ padding: 0 12px;
3662
+ border: 1px solid #d1d5db;
3663
+ border-radius: 6px;
3664
+ font-size: 12px;
3665
+ font-weight: 500;
3666
+ background: #ffffff;
3667
+ color: #374151;
3668
+ cursor: pointer;
3669
+ display: flex;
3670
+ align-items: center;
3671
+ gap: 5px;
3672
+ transition: background 0.15s, border-color 0.15s;
3673
+ }
3674
+
3675
+ .ds-toolbar-btn:hover {
3676
+ background: #f9fafb;
3677
+ border-color: #9ca3af;
3678
+ }
3679
+
3680
+ .ds-theme-dark .ds-toolbar-btn {
3681
+ background: #1e293b;
3682
+ border-color: #334155;
3683
+ color: #e2e8f0;
3684
+ }
3685
+
3686
+ .ds-theme-dark .ds-toolbar-btn:hover {
3687
+ background: #0f172a;
3688
+ }
3689
+
3690
+ /* ─────────────────────────────────────────────────────────────────────────────
3691
+ v3 Badge-bar filter panel
3692
+ Layout: [⊻ Filters:] [Filter A] [Filter B] [+ Add Filter]
3693
+ Per-filter popovers open below the clicked badge.
3694
+ ───────────────────────────────────────────────────────────────────────────── */
3695
+
3696
+ /* Wrapper — relative so popovers can be absolutely positioned inside */
3697
+ .ds-fp-v3 {
3698
+ position: relative;
3699
+ border-bottom: 1px solid #e5e7eb;
3700
+ background: inherit;
3701
+ }
3702
+
3703
+ .ds-theme-dark .ds-fp-v3 {
3704
+ border-bottom-color: #1e293b;
3705
+ }
3706
+
3707
+ /* ── Horizontal bar ─────────────────────────────────────────────────────────── */
3708
+ .ds-fp-v3-bar {
3709
+ display: flex;
3710
+ align-items: center;
3711
+ flex-wrap: wrap;
3712
+ gap: 6px;
3713
+ padding: 8px 16px;
3714
+ }
3715
+
3716
+ /* "⊻ Filters:" label */
3717
+ .ds-fp-v3-label {
3718
+ display: inline-flex;
3719
+ align-items: center;
3720
+ gap: 5px;
3721
+ font-size: 12px;
3722
+ font-weight: 600;
3723
+ color: #6b7280;
3724
+ letter-spacing: 0.03em;
3725
+ white-space: nowrap;
3726
+ flex-shrink: 0;
3727
+ margin-right: 2px;
3728
+ }
3729
+
3730
+ .ds-fp-v3-label svg {
3731
+ color: #9ca3af;
3732
+ }
3733
+
3734
+ .ds-theme-dark .ds-fp-v3-label {
3735
+ color: #94a3b8;
3736
+ }
3737
+
3738
+ /* Container for all badge pills */
3739
+ .ds-fp-v3-badges {
3740
+ display: flex;
3741
+ flex-wrap: wrap;
3742
+ align-items: center;
3743
+ gap: 6px;
3744
+ }
3745
+
3746
+ /* ── Filter badge pill ──────────────────────────────────────────────────────── */
3747
+ .ds-fp-v3-badge {
3748
+ display: inline-flex;
3749
+ align-items: center;
3750
+ gap: 5px;
3751
+ padding: 4px 12px;
3752
+ border: 1.5px solid #d1d5db;
3753
+ border-radius: 20px;
3754
+ background: #ffffff;
3755
+ color: #374151;
3756
+ font-size: 12px;
3757
+ font-weight: 500;
3758
+ cursor: pointer;
3759
+ white-space: nowrap;
3760
+ transition: border-color 0.15s, background 0.15s, color 0.15s, box-shadow 0.15s;
3761
+ line-height: 1.4;
3762
+ }
3763
+
3764
+ .ds-fp-v3-badge svg {
3765
+ flex-shrink: 0;
3766
+ color: #9ca3af;
3767
+ transition: color 0.15s;
3768
+ }
3769
+
3770
+ .ds-fp-v3-badge:hover {
3771
+ border-color: #6366f1;
3772
+ background: #f5f3ff;
3773
+ color: #4338ca;
3774
+ }
3775
+
3776
+ .ds-fp-v3-badge:hover svg {
3777
+ color: #6366f1;
3778
+ }
3779
+
3780
+ .ds-theme-dark .ds-fp-v3-badge {
3781
+ background: #1e293b;
3782
+ border-color: #334155;
3783
+ color: #cbd5e1;
3784
+ }
3785
+
3786
+ .ds-theme-dark .ds-fp-v3-badge:hover {
3787
+ border-color: #6366f1;
3788
+ background: #1e1e3a;
3789
+ color: #a5b4fc;
3790
+ }
3791
+
3792
+ /* Active state — green filled pill */
3793
+ .ds-fp-v3-badge-active {
3794
+ border-color: #16a34a;
3795
+ background: #16a34a;
3796
+ color: #ffffff;
3797
+ }
3798
+
3799
+ .ds-fp-v3-badge-active svg {
3800
+ color: rgba(255, 255, 255, 0.8);
3801
+ }
3802
+
3803
+ .ds-fp-v3-badge-active:hover {
3804
+ border-color: #15803d;
3805
+ background: #15803d;
3806
+ color: #ffffff;
3807
+ }
3808
+
3809
+ .ds-fp-v3-badge-active:hover svg {
3810
+ color: rgba(255, 255, 255, 0.9);
3811
+ }
3812
+
3813
+ .ds-theme-dark .ds-fp-v3-badge-active {
3814
+ border-color: #16a34a;
3815
+ background: #16a34a;
3816
+ color: #ffffff;
3817
+ }
3818
+
3819
+ /* "+ Add Filter" ghost button */
3820
+ .ds-fp-v3-add {
3821
+ display: inline-flex;
3822
+ align-items: center;
3823
+ gap: 4px;
3824
+ padding: 4px 12px;
3825
+ border: 1.5px dashed #d1d5db;
3826
+ border-radius: 20px;
3827
+ background: transparent;
3828
+ color: #9ca3af;
3829
+ font-size: 12px;
3830
+ font-weight: 500;
3831
+ cursor: pointer;
3832
+ white-space: nowrap;
3833
+ transition: border-color 0.15s, color 0.15s;
3834
+ line-height: 1.4;
3835
+ }
3836
+
3837
+ .ds-fp-v3-add:hover {
3838
+ border-color: #6366f1;
3839
+ color: #6366f1;
3840
+ }
3841
+
3842
+ .ds-theme-dark .ds-fp-v3-add {
3843
+ border-color: #334155;
3844
+ color: #64748b;
3845
+ }
3846
+
3847
+ .ds-theme-dark .ds-fp-v3-add:hover {
3848
+ border-color: #6366f1;
3849
+ color: #818cf8;
3850
+ }
3851
+
3852
+ /* ── Popover ────────────────────────────────────────────────────────────────── */
3853
+ .ds-fp-v3-pop {
3854
+ position: absolute;
3855
+ z-index: 9999;
3856
+ width: 300px;
3857
+ background: #ffffff;
3858
+ border: 1px solid #e5e7eb;
3859
+ border-radius: 12px;
3860
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);
3861
+ overflow: hidden;
3862
+ }
3863
+
3864
+ .ds-theme-dark .ds-fp-v3-pop {
3865
+ background: #1e293b;
3866
+ border-color: #334155;
3867
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
3868
+ }
3869
+
3870
+ /* Popover header */
3871
+ .ds-fp-v3-pop-hd {
3872
+ display: flex;
3873
+ align-items: center;
3874
+ justify-content: space-between;
3875
+ padding: 10px 12px 10px 14px;
3876
+ border-bottom: 1px solid #f3f4f6;
3877
+ background: #fafafa;
3878
+ }
3879
+
3880
+ .ds-theme-dark .ds-fp-v3-pop-hd {
3881
+ background: #0f172a;
3882
+ border-bottom-color: #334155;
3883
+ }
3884
+
3885
+ .ds-fp-v3-pop-title {
3886
+ display: inline-flex;
3887
+ align-items: center;
3888
+ gap: 6px;
3889
+ font-size: 13px;
3890
+ font-weight: 600;
3891
+ color: #374151;
3892
+ }
3893
+
3894
+ .ds-fp-v3-pop-title svg {
3895
+ color: #6b7280;
3896
+ flex-shrink: 0;
3897
+ }
3898
+
3899
+ .ds-theme-dark .ds-fp-v3-pop-title {
3900
+ color: #e2e8f0;
3901
+ }
3902
+
3903
+ .ds-theme-dark .ds-fp-v3-pop-title svg {
3904
+ color: #94a3b8;
3905
+ }
3906
+
3907
+ /* Action icon buttons (trash + close) */
3908
+ .ds-fp-v3-pop-actions {
3909
+ display: flex;
3910
+ align-items: center;
3911
+ gap: 4px;
3912
+ }
3913
+
3914
+ .ds-fp-v3-pop-icon-btn {
3915
+ width: 26px;
3916
+ height: 26px;
3917
+ display: flex;
3918
+ align-items: center;
3919
+ justify-content: center;
3920
+ border: 1px solid #e5e7eb;
3921
+ border-radius: 6px;
3922
+ background: transparent;
3923
+ color: #6b7280;
3924
+ cursor: pointer;
3925
+ padding: 0;
3926
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
3927
+ flex-shrink: 0;
3928
+ }
3929
+
3930
+ .ds-fp-v3-pop-icon-btn:hover {
3931
+ border-color: #6366f1;
3932
+ color: #6366f1;
3933
+ background: #eef2ff;
3934
+ }
3935
+
3936
+ .ds-theme-dark .ds-fp-v3-pop-icon-btn {
3937
+ border-color: #334155;
3938
+ color: #94a3b8;
3939
+ }
3940
+
3941
+ .ds-theme-dark .ds-fp-v3-pop-icon-btn:hover {
3942
+ border-color: #6366f1;
3943
+ color: #818cf8;
3944
+ background: #1e1e3a;
3945
+ }
3946
+
3947
+ /* Popover body */
3948
+ .ds-fp-v3-pop-body {
3949
+ display: flex;
3950
+ flex-direction: column;
3951
+ gap: 10px;
3952
+ padding: 12px 14px;
3953
+ }
3954
+
3955
+ /* Field row (label + control) */
3956
+ .ds-fp-v3-field {
3957
+ display: flex;
3958
+ flex-direction: column;
3959
+ gap: 5px;
3960
+ }
3961
+
3962
+ .ds-fp-v3-field-lbl {
3963
+ font-size: 10px;
3964
+ font-weight: 700;
3965
+ text-transform: uppercase;
3966
+ letter-spacing: 0.06em;
3967
+ color: #9ca3af;
3968
+ }
3969
+
3970
+ .ds-theme-dark .ds-fp-v3-field-lbl {
3971
+ color: #64748b;
3972
+ }
3973
+
3974
+ /* Condition select */
3975
+ .ds-fp-v3-condition {
3976
+ width: 100%;
3977
+ height: 32px;
3978
+ padding: 0 8px;
3979
+ border: 1px solid #d1d5db;
3980
+ border-radius: 7px;
3981
+ font-size: 12px;
3982
+ background: #ffffff;
3983
+ color: #374151;
3984
+ cursor: pointer;
3985
+ outline: none;
3986
+ transition: border-color 0.15s;
3987
+ }
3988
+
3989
+ .ds-fp-v3-condition:focus {
3990
+ border-color: #6366f1;
3991
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
3992
+ }
3993
+
3994
+ .ds-theme-dark .ds-fp-v3-condition {
3995
+ background: #0f172a;
3996
+ border-color: #334155;
3997
+ color: #e2e8f0;
3998
+ }
3999
+
4000
+ /* ── Multi-select widget ────────────────────────────────────────────────────── */
4001
+ .ds-fp-v3-ms-wrap {
4002
+ display: flex;
4003
+ flex-direction: column;
4004
+ gap: 6px;
4005
+ }
4006
+
4007
+ /* Trigger row */
4008
+ .ds-fp-v3-ms-trigger {
4009
+ display: flex;
4010
+ align-items: center;
4011
+ justify-content: space-between;
4012
+ gap: 6px;
4013
+ height: 32px;
4014
+ padding: 0 10px;
4015
+ border: 1px solid #d1d5db;
4016
+ border-radius: 7px;
4017
+ background: #ffffff;
4018
+ cursor: pointer;
4019
+ font-size: 12px;
4020
+ color: #374151;
4021
+ transition: border-color 0.15s;
4022
+ user-select: none;
4023
+ }
4024
+
4025
+ .ds-fp-v3-ms-trigger:hover {
4026
+ border-color: #6366f1;
4027
+ }
4028
+
4029
+ .ds-theme-dark .ds-fp-v3-ms-trigger {
4030
+ background: #0f172a;
4031
+ border-color: #334155;
4032
+ color: #e2e8f0;
4033
+ }
4034
+
4035
+ .ds-fp-v3-ms-display {
4036
+ flex: 1;
4037
+ overflow: hidden;
4038
+ text-overflow: ellipsis;
4039
+ white-space: nowrap;
4040
+ color: inherit;
4041
+ }
4042
+
4043
+ /* When no value selected, show placeholder colour */
4044
+ .ds-fp-v3-ms-trigger:not(.ds-fp-v3-ms-has-value) .ds-fp-v3-ms-display {
4045
+ color: #9ca3af;
4046
+ }
4047
+
4048
+ /* Dropdown panel */
4049
+ .ds-fp-v3-ms-drop {
4050
+ border: 1px solid #e5e7eb;
4051
+ border-radius: 8px;
4052
+ overflow: hidden;
4053
+ background: #ffffff;
4054
+ }
4055
+
4056
+ .ds-theme-dark .ds-fp-v3-ms-drop {
4057
+ background: #0f172a;
4058
+ border-color: #334155;
4059
+ }
4060
+
4061
+ /* Search input inside dropdown */
4062
+ .ds-fp-v3-ms-search {
4063
+ width: 100%;
4064
+ height: 32px;
4065
+ padding: 0 10px;
4066
+ border: none;
4067
+ border-bottom: 1px solid #f3f4f6;
4068
+ outline: none;
4069
+ font-size: 12px;
4070
+ background: #f9fafb;
4071
+ color: #374151;
4072
+ transition: background 0.15s;
4073
+ }
4074
+
4075
+ .ds-fp-v3-ms-search:focus {
4076
+ background: #ffffff;
4077
+ border-bottom-color: #6366f1;
4078
+ }
4079
+
4080
+ .ds-theme-dark .ds-fp-v3-ms-search {
4081
+ background: #0f172a;
4082
+ border-bottom-color: #1e293b;
4083
+ color: #e2e8f0;
4084
+ }
4085
+
4086
+ .ds-fp-v3-ms-search::placeholder {
4087
+ color: #9ca3af;
4088
+ }
4089
+
4090
+ /* Options list */
4091
+ .ds-fp-v3-ms-opts {
4092
+ max-height: 160px;
4093
+ overflow-y: auto;
4094
+ }
4095
+
4096
+ /* Individual option row */
4097
+ .ds-fp-v3-opt {
4098
+ display: flex;
4099
+ align-items: center;
4100
+ gap: 8px;
4101
+ padding: 6px 10px;
4102
+ cursor: pointer;
4103
+ font-size: 12px;
4104
+ color: #374151;
4105
+ transition: background 0.1s;
4106
+ user-select: none;
4107
+ }
4108
+
4109
+ .ds-fp-v3-opt:hover {
4110
+ background: #f0f0ff;
4111
+ }
4112
+
4113
+ .ds-fp-v3-opt-on {
4114
+ background: #eef2ff;
4115
+ }
4116
+
4117
+ .ds-theme-dark .ds-fp-v3-opt {
4118
+ color: #cbd5e1;
4119
+ }
4120
+
4121
+ .ds-theme-dark .ds-fp-v3-opt:hover {
4122
+ background: #1e293b;
4123
+ }
4124
+
4125
+ .ds-theme-dark .ds-fp-v3-opt-on {
4126
+ background: #1e293b;
4127
+ }
4128
+
4129
+ /* Custom checkbox */
4130
+ .ds-fp-v3-cb {
4131
+ width: 15px;
4132
+ height: 15px;
4133
+ border: 2px solid #d1d5db;
4134
+ border-radius: 3px;
4135
+ flex-shrink: 0;
4136
+ display: flex;
4137
+ align-items: center;
4138
+ justify-content: center;
4139
+ transition: border-color 0.1s, background 0.1s;
4140
+ }
4141
+
4142
+ .ds-fp-v3-cb-on {
4143
+ border-color: #6366f1;
4144
+ background: #6366f1;
4145
+ }
4146
+
4147
+ .ds-fp-v3-cb-on svg {
4148
+ stroke: #fff;
4149
+ }
4150
+
4151
+ /* Options empty/loading message */
4152
+ .ds-fp-v3-opts-msg {
4153
+ display: flex;
4154
+ align-items: center;
4155
+ justify-content: center;
4156
+ gap: 8px;
4157
+ padding: 12px;
4158
+ font-size: 12px;
4159
+ color: #9ca3af;
4160
+ }
4161
+
4162
+ /* ── Chips row (selected values) ────────────────────────────────────────────── */
4163
+ .ds-fp-v3-chips {
4164
+ display: flex;
4165
+ flex-wrap: wrap;
4166
+ gap: 4px;
4167
+ }
4168
+
4169
+ .ds-fp-v3-chip {
4170
+ display: inline-flex;
4171
+ align-items: center;
4172
+ gap: 4px;
4173
+ padding: 2px 8px;
4174
+ border-radius: 10px;
4175
+ background: #e0e7ff;
4176
+ color: #4338ca;
4177
+ font-size: 11px;
4178
+ font-weight: 600;
4179
+ white-space: nowrap;
4180
+ }
4181
+
4182
+ .ds-theme-dark .ds-fp-v3-chip {
4183
+ background: #312e81;
4184
+ color: #a5b4fc;
4185
+ }
4186
+
4187
+ .ds-fp-v3-chip-x {
4188
+ border: none;
4189
+ background: transparent;
4190
+ color: inherit;
4191
+ font-size: 10px;
4192
+ cursor: pointer;
4193
+ padding: 0;
4194
+ line-height: 1;
4195
+ opacity: 0.7;
4196
+ transition: opacity 0.1s;
4197
+ }
4198
+
4199
+ .ds-fp-v3-chip-x:hover {
4200
+ opacity: 1;
4201
+ }
4202
+
4203
+ /* ── Popover footer ─────────────────────────────────────────────────────────── */
4204
+ .ds-fp-v3-pop-footer {
4205
+ display: flex;
4206
+ align-items: center;
4207
+ justify-content: flex-end;
4208
+ gap: 8px;
4209
+ padding: 10px 14px;
4210
+ border-top: 1px solid #f3f4f6;
4211
+ background: #fafafa;
4212
+ }
4213
+
4214
+ .ds-theme-dark .ds-fp-v3-pop-footer {
4215
+ background: #0f172a;
4216
+ border-top-color: #334155;
4217
+ }
4218
+
4219
+ .ds-fp-v3-clear-btn {
4220
+ height: 30px;
4221
+ padding: 0 14px;
4222
+ border: 1px solid #d1d5db;
4223
+ border-radius: 7px;
4224
+ background: #ffffff;
4225
+ color: #374151;
4226
+ font-size: 12px;
4227
+ font-weight: 500;
4228
+ cursor: pointer;
4229
+ transition: border-color 0.15s, background 0.15s;
4230
+ }
4231
+
4232
+ .ds-fp-v3-clear-btn:hover {
4233
+ border-color: #9ca3af;
4234
+ background: #f3f4f6;
4235
+ }
4236
+
4237
+ .ds-theme-dark .ds-fp-v3-clear-btn {
4238
+ background: #1e293b;
4239
+ border-color: #334155;
4240
+ color: #e2e8f0;
4241
+ }
4242
+
4243
+ .ds-theme-dark .ds-fp-v3-clear-btn:hover {
4244
+ background: #0f172a;
4245
+ }
4246
+
4247
+ .ds-fp-v3-apply-btn {
4248
+ height: 30px;
4249
+ padding: 0 16px;
4250
+ border: none;
4251
+ border-radius: 7px;
4252
+ background: linear-gradient(135deg, #16a34a, #15803d);
4253
+ color: #ffffff;
4254
+ font-size: 12px;
4255
+ font-weight: 600;
4256
+ cursor: pointer;
4257
+ transition: opacity 0.15s, transform 0.1s;
4258
+ white-space: nowrap;
4259
+ }
4260
+
4261
+ .ds-fp-v3-apply-btn:hover {
4262
+ opacity: 0.9;
4263
+ }
4264
+
4265
+ .ds-fp-v3-apply-btn:active {
4266
+ transform: scale(0.97);
4267
+ }
4268
+
4269
+ /* ── Date range inputs ──────────────────────────────────────────────────────── */
4270
+ .ds-fp-v3-date-range {
4271
+ display: flex;
4272
+ align-items: center;
4273
+ gap: 6px;
4274
+ }
4275
+
4276
+ .ds-fp-v3-date {
4277
+ flex: 1;
4278
+ height: 32px;
4279
+ padding: 0 8px;
4280
+ border: 1px solid #d1d5db;
4281
+ border-radius: 7px;
4282
+ font-size: 12px;
4283
+ background: #ffffff;
4284
+ color: #374151;
4285
+ outline: none;
4286
+ transition: border-color 0.15s;
4287
+ }
4288
+
4289
+ .ds-fp-v3-date:focus {
4290
+ border-color: #6366f1;
4291
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
4292
+ }
4293
+
4294
+ .ds-theme-dark .ds-fp-v3-date {
4295
+ background: #0f172a;
4296
+ border-color: #334155;
4297
+ color: #e2e8f0;
4298
+ }
4299
+
4300
+ .ds-fp-v3-date-sep {
4301
+ color: #9ca3af;
4302
+ font-size: 12px;
4303
+ flex-shrink: 0;
4304
+ }
4305
+
4306
+ /* ── Text / number input ────────────────────────────────────────────────────── */
4307
+ .ds-fp-v3-text-input {
4308
+ width: 100%;
4309
+ height: 32px;
4310
+ padding: 0 10px;
4311
+ border: 1px solid #d1d5db;
4312
+ border-radius: 7px;
4313
+ font-size: 12px;
4314
+ background: #ffffff;
4315
+ color: #374151;
4316
+ outline: none;
4317
+ transition: border-color 0.15s;
4318
+ }
4319
+
4320
+ .ds-fp-v3-text-input:focus {
4321
+ border-color: #6366f1;
4322
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.15);
4323
+ }
4324
+
4325
+ .ds-theme-dark .ds-fp-v3-text-input {
4326
+ background: #0f172a;
4327
+ border-color: #334155;
4328
+ color: #e2e8f0;
4329
+ }
4330
+ `, e = document.createElement("style");
4331
+ e.id = "deepspot-sdk-styles", e.textContent = P, document.head.appendChild(e);
4332
+ } catch (P) {
4333
+ }
4334
+ }
4335
+ }
4336
+ function te(P) {
4337
+ if (typeof P == "string") {
4338
+ const e = document.querySelector(P);
4339
+ if (!e) throw new Error(`Deepspot SDK: container "${P}" not found in DOM`);
4340
+ return e;
4341
+ }
4342
+ return P;
4343
+ }
4344
+ class fe {
4345
+ constructor(e) {
4346
+ if (!e.apiKey) throw new Error("Deepspot SDK: apiKey is required");
4347
+ if (!e.baseUrl) throw new Error("Deepspot SDK: baseUrl is required");
4348
+ this.apiClient = new ie(e.baseUrl, e.apiKey), pe();
4349
+ }
4350
+ // ── Embed full dashboard ────────────────────────────────────────────────────
4351
+ async embedDashboard(e) {
4352
+ var i, a, f, h, v, m, p, n, g, u;
4353
+ const t = te(e.container), r = (i = e.embedLevel) != null ? i : "dashboard", s = (a = e.theme) != null ? a : "light";
4354
+ t.style.height = e.height || "600px", t.style.display = "block";
4355
+ const o = this.createRoot(t, s);
4356
+ this.showLoading(o);
4357
+ try {
4358
+ const b = await this.apiClient.getEmbedToken({
4359
+ dashboardId: e.dashboardId,
4360
+ embedType: "dashboard",
4361
+ embedLevel: r,
4362
+ userId: e.userId,
4363
+ tenantId: e.tenantId
4364
+ }), l = await this.apiClient.getDashboardRender(
4365
+ e.dashboardId,
4366
+ b,
4367
+ {
4368
+ embedLevel: r,
4369
+ pageId: e.pageId,
4370
+ tabId: e.tabId,
4371
+ filters: e.filters || {}
4372
+ }
4373
+ );
4374
+ let d;
4375
+ const c = new D(o, {
4376
+ embedLevel: r,
4377
+ theme: s,
4378
+ // v1 backward compat (hideFilters → hideGlobalFilters)
4379
+ hideFilters: (f = e.hideGlobalFilters) != null ? f : !1,
4380
+ hideExport: (h = e.hideExport) != null ? h : !1,
4381
+ // v2 visibility
4382
+ hideGlobalFilters: (v = e.hideGlobalFilters) != null ? v : !1,
4383
+ hideInlineFilters: (m = e.hideInlineFilters) != null ? m : !1,
4384
+ hideText: (p = e.hideText) != null ? p : !1,
4385
+ hideHeader: (n = e.hideHeader) != null ? n : !1,
4386
+ hideBackground: (g = e.hideBackground) != null ? g : !1,
4387
+ initialFilters: e.filters || {},
4388
+ onFilterChange: e.onFilterChange,
4389
+ onReady: e.onReady,
4390
+ onFetchFilterOptions: ($) => this.apiClient.getFilterOptions(e.dashboardId, $, b),
4391
+ onFetchTablePage: ($, w, k) => this.apiClient.getTablePage(e.dashboardId, $, w, k, b),
4392
+ onTabSwitch: async ($, w) => {
4393
+ var k;
4394
+ (k = e.onTabSwitch) == null || k.call(e, $, w);
4395
+ try {
4396
+ const x = await this.apiClient.getDashboardRender(
4397
+ e.dashboardId,
4398
+ b,
4399
+ {
4400
+ embedLevel: r,
4401
+ pageId: $,
4402
+ tabId: w,
4403
+ // Use renderer's live filter state — instance.filters is a stale copy
4404
+ filters: c.getActiveFilters(),
4405
+ filterOperators: c.getActiveFilterOperators(),
4406
+ localFilters: c.getActiveLocalFilters()
4407
+ }
4408
+ );
4409
+ c.update(x);
4410
+ } catch (x) {
4411
+ console.error("Deepspot SDK: tab fetch failed", x);
4412
+ }
4413
+ }
4414
+ });
4415
+ return c.render(l), d = new Z({
4416
+ dashboardId: e.dashboardId,
4417
+ token: b,
4418
+ embedType: "dashboard",
4419
+ embedLevel: r,
4420
+ activePageId: l.activePage,
4421
+ activeTabId: l.activeTab,
4422
+ activeFilters: e.filters || {},
4423
+ apiClient: this.apiClient,
4424
+ renderer: c,
4425
+ onFilterChange: e.onFilterChange
4426
+ }), d;
4427
+ } catch (b) {
4428
+ throw this.showError(o, (b == null ? void 0 : b.message) || "Failed to load dashboard"), (u = e.onError) == null || u.call(e, (b == null ? void 0 : b.message) || "Failed to load dashboard"), b;
4429
+ }
4430
+ }
4431
+ // ── Embed single report (component) ────────────────────────────────────────
4432
+ async embedReport(e) {
4433
+ var s;
4434
+ const t = te(e.container);
4435
+ t.style.height = e.height || "400px", t.style.display = "block";
4436
+ const r = this.createRoot(t, e.theme || "light");
4437
+ this.showLoading(r);
4438
+ try {
4439
+ const o = await this.apiClient.getEmbedToken({
4440
+ dashboardId: e.dashboardId,
4441
+ embedType: "report",
4442
+ componentId: e.componentId,
4443
+ userId: e.userId,
4444
+ tenantId: e.tenantId
4445
+ }), i = await this.apiClient.getReportRender(
4446
+ e.dashboardId,
4447
+ e.componentId,
4448
+ o,
4449
+ { filters: e.filters || {} }
4450
+ ), a = new ce(r, e.theme || "light");
4451
+ return a.render(i, e.onReady), new Z({
4452
+ dashboardId: e.dashboardId,
4453
+ componentId: e.componentId,
4454
+ token: o,
4455
+ embedType: "report",
4456
+ activeFilters: e.filters || {},
4457
+ apiClient: this.apiClient,
4458
+ renderer: a
4459
+ });
4460
+ } catch (o) {
4461
+ throw this.showError(r, (o == null ? void 0 : o.message) || "Failed to load report"), (s = e.onError) == null || s.call(e, (o == null ? void 0 : o.message) || "Failed to load report"), o;
4462
+ }
4463
+ }
4464
+ // ── Private helpers ─────────────────────────────────────────────────────────
4465
+ createRoot(e, t) {
4466
+ e.innerHTML = "";
4467
+ const r = document.createElement("div");
4468
+ return r.className = `ds-embed-root ds-theme-${t}`, r.style.width = "100%", r.style.height = "100%", e.appendChild(r), r;
4469
+ }
4470
+ showLoading(e) {
4471
+ e.innerHTML = `
4472
+ <div class="ds-embed-loading">
4473
+ <div class="ds-embed-spinner"></div>
4474
+ <div class="ds-embed-loading-text">Loading…</div>
4475
+ </div>
4476
+ `;
4477
+ }
4478
+ showError(e, t) {
4479
+ e.innerHTML = `
4480
+ <div class="ds-embed-error">
4481
+ <div class="ds-embed-error-icon">⚠</div>
4482
+ <div>${t}</div>
4483
+ <div class="ds-embed-error-message">Check your SDK configuration.</div>
4484
+ </div>
4485
+ `;
4486
+ }
4487
+ }
4488
+ export {
4489
+ fe as DeepspotSDK
4490
+ };