@burdenoff/microfe-bigconsole 2026.730.2 → 2026.730.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +73 -68
  2. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  3. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js +82 -0
  4. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js.map +1 -0
  5. package/dist/bigconsole/components/embed/EmbedErrorBoundary.js +25 -0
  6. package/dist/bigconsole/components/embed/EmbedErrorBoundary.js.map +1 -0
  7. package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js +36 -0
  8. package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js.map +1 -0
  9. package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js +62 -0
  10. package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js.map +1 -0
  11. package/dist/bigconsole/components/embed/index.js +7 -0
  12. package/dist/bigconsole/components/embed/renderer-manifest.js +77 -0
  13. package/dist/bigconsole/components/embed/renderer-manifest.js.map +1 -0
  14. package/dist/bigconsole/components/embed/types.js +6 -0
  15. package/dist/bigconsole/components/embed/types.js.map +1 -0
  16. package/dist/bigconsole/components/embed/validate.js +29 -0
  17. package/dist/bigconsole/components/embed/validate.js.map +1 -0
  18. package/dist/bigconsole/components/embed/widgets/KpiComparisonEmbed.js +34 -0
  19. package/dist/bigconsole/components/embed/widgets/KpiComparisonEmbed.js.map +1 -0
  20. package/dist/bigconsole/components/embed/widgets/ListEmbed.js +36 -0
  21. package/dist/bigconsole/components/embed/widgets/ListEmbed.js.map +1 -0
  22. package/dist/bigconsole/components/embed/widgets/MetricCardEmbed.js +20 -0
  23. package/dist/bigconsole/components/embed/widgets/MetricCardEmbed.js.map +1 -0
  24. package/dist/bigconsole/components/embed/widgets/TableEmbed.js +40 -0
  25. package/dist/bigconsole/components/embed/widgets/TableEmbed.js.map +1 -0
  26. package/dist/bigconsole/components/embed/widgets/TextEmbed.js +20 -0
  27. package/dist/bigconsole/components/embed/widgets/TextEmbed.js.map +1 -0
  28. package/dist/bigconsole/components/embed/widgets/shared.js +29 -0
  29. package/dist/bigconsole/components/embed/widgets/shared.js.map +1 -0
  30. package/dist/bigconsole/components/widgets/WidgetWrapper.js +2 -1
  31. package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
  32. package/dist/bigconsole/components/widgets/states/EmptyState.js +3 -3
  33. package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
  34. package/package.json +5 -1
@@ -15,36 +15,37 @@ import u from "./DashboardToolbar.js";
15
15
  import d from "./GlobalFiltersBar/GlobalFiltersBar.js";
16
16
  import "./GlobalFiltersBar/index.js";
17
17
  import ee from "./LiveBoardBanner.js";
18
- import te from "./PageTabsManager/PageTabsManager.js";
18
+ import te from "./TemplateSetupBanner.js";
19
+ import ne from "./PageTabsManager/PageTabsManager.js";
19
20
  import "./PageTabsManager/index.js";
20
- import ne from "./ShareDialog/ShareDialog.js";
21
+ import re from "./ShareDialog/ShareDialog.js";
21
22
  import "./ShareDialog/index.js";
22
- import re from "./WidgetPalette.js";
23
- import { WidgetInspector as ie } from "../builder/WidgetInspector.js";
23
+ import ie from "./WidgetPalette.js";
24
+ import { WidgetInspector as ae } from "../builder/WidgetInspector.js";
24
25
  import "../builder/index.js";
25
- import { calculatePositionFromDrop as ae, findNextAvailablePosition as oe } from "../../utils/widgetPositioning.js";
26
- import { memo as f, useCallback as p, useEffect as se, useMemo as ce, useRef as m, useState as h } from "react";
26
+ import { calculatePositionFromDrop as oe, findNextAvailablePosition as se } from "../../utils/widgetPositioning.js";
27
+ import { memo as f, useCallback as p, useEffect as ce, useMemo as le, useRef as m, useState as h } from "react";
27
28
  import { jsx as g, jsxs as _ } from "react/jsx-runtime";
28
29
  //#region src/bigconsole/components/dashboard/DashboardCanvas.tsx
29
- var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: ue, isSaving: de = !1, onExport: fe, pages: b = [], activePageId: pe, onPageChange: me, onPagesReorder: x, onPageAdd: he, onPageRename: ge, onPageDuplicate: _e, onPageDelete: ve, showFilters: ye = !0, showPageTabs: be = !0, enableCrossFilter: xe = !0 }) {
30
- let S = m(null), C = m(null), [w, Se] = h(1200), [Ce, T] = h(!1), [E, D] = h(null), [O, k] = h(!1), [A, j] = h(null), M = t((e) => e.viewMode === "edit"), { workspaceId: N, apiGatewayUrl: P, authToken: F } = r(), { installedWidgets: we, loading: Te } = c({
30
+ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: ue, onSave: de, isSaving: b = !1, onExport: fe, pages: x = [], activePageId: pe, onPageChange: me, onPagesReorder: S, onPageAdd: he, onPageRename: ge, onPageDuplicate: _e, onPageDelete: ve, showFilters: ye = !0, showPageTabs: be = !0, enableCrossFilter: xe = !0 }) {
31
+ let C = m(null), w = m(null), [T, Se] = h(1200), [Ce, E] = h(!1), [D, O] = h(null), [k, A] = h(!1), [we, j] = h(null), M = t((e) => e.viewMode === "edit"), { workspaceId: N, apiGatewayUrl: P, authToken: F } = r(), { installedWidgets: Te, loading: Ee } = c({
31
32
  workspaceId: N,
32
33
  apiGatewayUrl: P,
33
34
  authToken: F,
34
35
  enabled: M
35
- }), { definitions: Ee } = a({
36
+ }), { definitions: De } = a({
36
37
  workspaceId: N,
37
38
  apiGatewayUrl: P,
38
39
  authToken: F,
39
40
  enabled: M
40
- }), I = t((e) => e.zoomLevel), L = t((e) => e.sidebarCollapsed), R = t((e) => e.toggleSidebar), z = !L, B = t((e) => e.setGlobalFilterValues), De = p((e) => {
41
+ }), I = t((e) => e.zoomLevel), L = t((e) => e.sidebarCollapsed), R = t((e) => e.toggleSidebar), z = !L, B = t((e) => e.setGlobalFilterValues), Oe = p((e) => {
41
42
  B(e);
42
- }, [B]), { createWidget: V } = o(v, f, { skipFetch: !0 }), H = e((e) => e.widgets), U = ce(() => {
43
+ }, [B]), { createWidget: V } = o(v, f, { skipFetch: !0 }), H = e((e) => e.widgets), U = le(() => {
43
44
  let e = Array.from(H.values());
44
45
  return v ? e.filter((e) => e.pageId === v) : e;
45
- }, [H, v]), Oe = e((e) => e.selectedWidgetId), W = e((e) => e.selectWidget), G = e((e) => e.addWidget), K = e((e) => e.updateWidgetPosition), q = e((e) => e.configPanelOpen), ke = e((e) => e.closeConfigPanel);
46
- se(() => {
47
- let e = S.current;
46
+ }, [H, v]), ke = e((e) => e.selectedWidgetId), W = e((e) => e.selectWidget), G = e((e) => e.addWidget), K = e((e) => e.updateWidgetPosition), q = e((e) => e.configPanelOpen), Ae = e((e) => e.closeConfigPanel);
47
+ ce(() => {
48
+ let e = C.current;
48
49
  if (!e) return;
49
50
  let t = () => {
50
51
  Se(e.offsetWidth);
@@ -55,7 +56,7 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
55
56
  }
56
57
  return window.addEventListener("resize", t), () => window.removeEventListener("resize", t);
57
58
  }, []);
58
- let Ae = p((e) => {
59
+ let J = p((e) => {
59
60
  e.forEach((e) => {
60
61
  K(e.i, {
61
62
  x: e.x,
@@ -64,17 +65,17 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
64
65
  height: e.h
65
66
  });
66
67
  });
67
- }, [K]), J = p((e) => {
68
+ }, [K]), je = p((e) => {
68
69
  W(e);
69
70
  }, [W]), Y = p(async (e, t) => {
70
- D(null);
71
+ O(null);
71
72
  let n = t?.definition || (e.type ? i(e.type) : null), r = {
72
73
  width: n?.defaultSize?.width ?? 4,
73
74
  height: n?.defaultSize?.height ?? 4
74
75
  }, a;
75
76
  if (t?.position) a = t.position;
76
77
  else {
77
- let { x: e, y: t } = oe(U, r, { cols: 12 });
78
+ let { x: e, y: t } = se(U, r, { cols: 12 });
78
79
  a = {
79
80
  x: e,
80
81
  y: t,
@@ -102,7 +103,7 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
102
103
  W(t.id);
103
104
  return;
104
105
  }
105
- D("Could not create the widget. Check the connection and try again.");
106
+ O("Could not create the widget. Check the connection and try again.");
106
107
  return;
107
108
  }
108
109
  let s = {
@@ -135,12 +136,12 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
135
136
  G,
136
137
  W,
137
138
  U
138
- ]), je = p((e) => {
139
- k(!0), j(e);
140
- }, []), Me = p(() => {
141
- k(!1), j(null);
142
- }, []), Ne = p((e) => {
143
- e.preventDefault(), k(!1), j(null);
139
+ ]), Me = p((e) => {
140
+ A(!0), j(e);
141
+ }, []), Ne = p(() => {
142
+ A(!1), j(null);
143
+ }, []), Pe = p((e) => {
144
+ e.preventDefault(), A(!1), j(null);
144
145
  try {
145
146
  let t = e.dataTransfer.getData("application/json");
146
147
  if (!t) return;
@@ -154,12 +155,12 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
154
155
  defaultConfig: r.defaultConfig,
155
156
  defaultSize: r.defaultSize || a.defaultSize
156
157
  });
157
- let s = S.current?.getBoundingClientRect();
158
+ let s = C.current?.getBoundingClientRect();
158
159
  if (!s) return;
159
160
  let c = {
160
161
  width: a.defaultSize.width,
161
162
  height: a.defaultSize.height
162
- }, { x: l, y: u } = ae(e.clientX, e.clientY, s, w, c, { cols: 12 }), d = {
163
+ }, { x: l, y: u } = oe(e.clientX, e.clientY, s, T, c, { cols: 12 }), d = {
163
164
  x: l,
164
165
  y: u,
165
166
  width: c.width,
@@ -176,9 +177,9 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
176
177
  } catch (e) {
177
178
  console.error("Failed to handle widget drop:", e);
178
179
  }
179
- }, [w, Y]), Pe = p((e) => {
180
+ }, [T, Y]), Fe = p((e) => {
180
181
  e.preventDefault(), e.dataTransfer.dropEffect = "copy";
181
- }, []), Fe = p((e, t) => {
182
+ }, []), Ie = p((e, t) => {
182
183
  let n = t || i(e);
183
184
  Y({
184
185
  type: e,
@@ -191,8 +192,8 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
191
192
  L && R(), window.requestAnimationFrame(() => {
192
193
  document.querySelector("input[placeholder=\"Search widgets...\"]")?.focus();
193
194
  });
194
- }, [L, R]), { exportPNG: Q, exportPDF: $, isExporting: Ie } = s({
195
- containerRef: C,
195
+ }, [L, R]), { exportPNG: Q, exportPDF: $, isExporting: Le } = s({
196
+ containerRef: w,
196
197
  name: y || "dashboard"
197
198
  });
198
199
  return /* @__PURE__ */ _("div", {
@@ -204,9 +205,9 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
204
205
  onAddWidget: Z,
205
206
  onTogglePalette: X,
206
207
  isPaletteOpen: z,
207
- onPaste: le,
208
- onSave: ue,
209
- isSaving: de,
208
+ onPaste: ue,
209
+ onSave: de,
210
+ isSaving: b,
210
211
  onExport: fe,
211
212
  onExportImage: p(() => {
212
213
  Q();
@@ -214,35 +215,39 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
214
215
  onExportPdf: p(() => {
215
216
  $();
216
217
  }, [$]),
217
- isExportingImage: Ie,
218
- onShare: () => T(!0)
218
+ isExportingImage: Le,
219
+ onShare: () => E(!0)
219
220
  }),
220
- E && /* @__PURE__ */ _("div", {
221
+ D && /* @__PURE__ */ _("div", {
221
222
  className: "flex items-center justify-between gap-3 border-b border-status-error-border bg-status-error-bg px-4 py-2 text-sm text-status-error-text",
222
223
  role: "alert",
223
- children: [/* @__PURE__ */ g("span", { children: E }), /* @__PURE__ */ g("button", {
224
+ children: [/* @__PURE__ */ g("span", { children: D }), /* @__PURE__ */ g("button", {
224
225
  type: "button",
225
226
  className: "font-medium underline underline-offset-2",
226
- onClick: () => D(null),
227
+ onClick: () => O(null),
227
228
  children: "Dismiss"
228
229
  })]
229
230
  }),
231
+ /* @__PURE__ */ g(te, {
232
+ widgets: U,
233
+ isEditMode: M
234
+ }),
230
235
  /* @__PURE__ */ g(ee, {
231
236
  widgets: U,
232
237
  isEditMode: M
233
238
  }),
234
239
  ye && /* @__PURE__ */ g(d, {
235
240
  dashboardId: f,
236
- onFilterChange: De,
241
+ onFilterChange: Oe,
237
242
  collapsible: !0
238
243
  }),
239
- be && (M || b.length > 0) && /* @__PURE__ */ g(te, {
240
- pages: b,
241
- activePageId: pe || b[0]?.id,
244
+ be && (M || x.length > 0) && /* @__PURE__ */ g(ne, {
245
+ pages: x,
246
+ activePageId: pe || x[0]?.id,
242
247
  editable: M,
243
248
  onPageSelect: me || (() => {}),
244
- onPagesReorder: x ? (e) => {
245
- x(e.map((e) => b.find((t) => t.id === e)).filter(Boolean));
249
+ onPagesReorder: S ? (e) => {
250
+ S(e.map((e) => x.find((t) => t.id === e)).filter(Boolean));
246
251
  } : void 0,
247
252
  onPageCreate: he,
248
253
  onPageRename: ge,
@@ -255,30 +260,30 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
255
260
  marginRight: q ? "var(--bc-inspector-width, 420px)" : void 0,
256
261
  transition: "margin-right 0.2s ease"
257
262
  },
258
- children: [M && /* @__PURE__ */ g(re, {
263
+ children: [M && /* @__PURE__ */ g(ie, {
259
264
  isOpen: z,
260
265
  onClose: X,
261
- onWidgetSelect: Fe,
262
- onDragStart: je,
263
- onDragEnd: Me,
264
- installedWidgets: we,
265
- installedWidgetsLoading: Te,
266
- definitions: Ee
266
+ onWidgetSelect: Ie,
267
+ onDragStart: Me,
268
+ onDragEnd: Ne,
269
+ installedWidgets: Te,
270
+ installedWidgetsLoading: Ee,
271
+ definitions: De
267
272
  }), /* @__PURE__ */ _("div", {
268
- ref: S,
273
+ ref: C,
269
274
  className: "flex-1 min-w-0 overflow-auto bg-bg-sunken",
270
- onDrop: Ne,
271
- onDragOver: Pe,
275
+ onDrop: Pe,
276
+ onDragOver: Fe,
272
277
  onClick: () => {
273
278
  M && W(null);
274
279
  },
275
280
  style: {
276
281
  position: "relative",
277
- border: O ? "2px dashed var(--color-action-primary-bg)" : "none",
278
- backgroundColor: O ? "var(--color-bg-hover)" : void 0,
282
+ border: k ? "2px dashed var(--color-action-primary-bg)" : "none",
283
+ backgroundColor: k ? "var(--color-bg-hover)" : void 0,
279
284
  transition: "border-color 0.2s, background-color 0.2s"
280
285
  },
281
- children: [O && /* @__PURE__ */ g("div", {
286
+ children: [k && /* @__PURE__ */ g("div", {
282
287
  style: {
283
288
  position: "absolute",
284
289
  top: "50%",
@@ -300,12 +305,12 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
300
305
  },
301
306
  children: [
302
307
  "Drop to add ",
303
- /* @__PURE__ */ g("strong", { children: A }),
308
+ /* @__PURE__ */ g("strong", { children: we }),
304
309
  " widget"
305
310
  ]
306
311
  })
307
312
  }), /* @__PURE__ */ g("div", {
308
- ref: C,
313
+ ref: w,
309
314
  className: "min-h-full origin-top-left transition-transform",
310
315
  style: {
311
316
  transform: `scale(${I / 100})`,
@@ -317,10 +322,10 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
317
322
  children: /* @__PURE__ */ g(l, {
318
323
  widgets: U,
319
324
  isEditMode: M,
320
- width: w,
321
- onLayoutChange: Ae,
322
- onWidgetSelect: J,
323
- selectedWidgetId: Oe
325
+ width: T,
326
+ onLayoutChange: J,
327
+ onWidgetSelect: je,
328
+ selectedWidgetId: ke
324
329
  })
325
330
  }) : /* @__PURE__ */ _("div", {
326
331
  className: "flex flex-col items-center justify-center min-h-[400px] p-8",
@@ -370,15 +375,15 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
370
375
  })]
371
376
  })]
372
377
  }),
373
- /* @__PURE__ */ g(ie, {
378
+ /* @__PURE__ */ g(ae, {
374
379
  isOpen: q,
375
- onClose: ke
380
+ onClose: Ae
376
381
  }),
377
- /* @__PURE__ */ g(ne, {
382
+ /* @__PURE__ */ g(re, {
378
383
  dashboardId: f,
379
384
  dashboardName: y || "Dashboard",
380
385
  isOpen: Ce,
381
- onClose: () => T(!1)
386
+ onClose: () => E(!1)
382
387
  })
383
388
  ]
384
389
  });
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport type { Layout } from 'react-grid-layout';\nimport type { Widget, WidgetType } from '../../types';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useDashboardImageExport } from '../../hooks/useDashboardImageExport';\nimport { useBigConsole } from '../../context';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { LiveBoardBanner } from './LiveBoardBanner';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetInspector } from '../builder';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\ninterface WidgetPlacement {\n position: { x: number; y: number; width: number; height: number };\n definition?: WidgetDefinition | null;\n}\n\ninterface PaletteDropData {\n type: WidgetType;\n name?: string;\n category?: WidgetDefinition['category'];\n defaultConfig?: Record<string, unknown>;\n defaultSize?: WidgetDefinition['defaultSize'];\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n // Inner board element (the grid surface) — rasterized for PNG/PDF board-pack export.\n const boardRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [widgetCreateError, setWidgetCreateError] = useState<string | null>(null);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const sidebarCollapsed = useDashboardStore((state) => state.sidebarCollapsed);\n const toggleSidebar = useDashboardStore((state) => state.toggleSidebar);\n const isPaletteOpen = !sidebarCollapsed;\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Keep react-grid-layout's explicit width in sync with the actual canvas.\n // A ResizeObserver is required because opening the docked inspector or widget\n // palette resizes this element without resizing the browser window.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateWidth = () => {\n setContainerWidth(container.offsetWidth);\n };\n\n updateWidth();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(updateWidth);\n observer.observe(container);\n return () => observer.disconnect();\n }\n\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Create with defaults, select the result, then configure it in the inspector.\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>, placement?: WidgetPlacement) => {\n setWidgetCreateError(null);\n const definition = placement?.definition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n let position: { x: number; y: number; width: number; height: number };\n if (placement?.position) {\n position = placement.position;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Preserve legacy binding fields when creating from imported/default data.\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n return;\n }\n\n setWidgetCreateError('Could not create the widget. Check the connection and try again.');\n return;\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n },\n [dashboardId, pageId, createWidgetApi, addWidgetToStore, selectWidget, widgetsArray]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - create immediately with defaults.\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const parsedData: unknown = JSON.parse(data);\n if (!parsedData || typeof parsedData !== 'object' || !('type' in parsedData)) return;\n const widgetData = parsedData as PaletteDropData;\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: definition.defaultSize.width,\n height: definition.defaultSize.height,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: 12 }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n void handleAddWidget(\n {\n type: widgetData.type,\n title: widgetData.name || definition.name,\n config: widgetData.defaultConfig || definition.defaultConfig,\n },\n { position, definition: droppedDefinition }\n );\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth, handleAddWidget]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n toggleSidebar();\n }, [toggleSidebar]);\n\n const openWidgetPalette = useCallback(() => {\n if (sidebarCollapsed) toggleSidebar();\n window.requestAnimationFrame(() => {\n document.querySelector<HTMLInputElement>('input[placeholder=\"Search widgets...\"]')?.focus();\n });\n }, [sidebarCollapsed, toggleSidebar]);\n\n // Board-pack export (PNG + PDF) — rasterizes the inner board element.\n const {\n exportPNG,\n exportPDF,\n isExporting: isExportingImage,\n } = useDashboardImageExport({ containerRef: boardRef, name: title || 'dashboard' });\n const handleExportImage = useCallback(() => {\n void exportPNG();\n }, [exportPNG]);\n const handleExportPdf = useCallback(() => {\n void exportPDF();\n }, [exportPDF]);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={openWidgetPalette}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onExportImage={handleExportImage}\n onExportPdf={handleExportPdf}\n isExportingImage={isExportingImage}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {widgetCreateError && (\n <div\n className=\"flex items-center justify-between gap-3 border-b border-status-error-border bg-status-error-bg px-4 py-2 text-sm text-status-error-text\"\n role=\"alert\"\n >\n <span>{widgetCreateError}</span>\n <button\n type=\"button\"\n className=\"font-medium underline underline-offset-2\"\n onClick={() => setWidgetCreateError(null)}\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Live wall-board freshness banner — renders only when the page has\n auto-refreshing widgets (e.g. the Live Bed Board) and not in edit mode. */}\n <LiveBoardBanner widgets={widgetsArray} isEditMode={isEditMode} />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas.\n When the properties inspector is open we reserve equal space on the\n right so the (fixed, non-modal) panel docks beside the canvas instead\n of overlaying the selected widget. */}\n <div\n className=\"flex flex-1 min-w-0 overflow-hidden\"\n style={{\n marginRight: configPanelOpen ? 'var(--bc-inspector-width, 420px)' : undefined,\n transition: 'margin-right 0.2s ease',\n }}\n >\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={togglePalette}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/* Canvas area */}\n <div\n ref={containerRef}\n className=\"flex-1 min-w-0 overflow-auto bg-bg-sunken\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={{\n position: 'relative',\n border: isDraggingWidget ? '2px dashed var(--color-action-primary-bg)' : 'none',\n backgroundColor: isDraggingWidget ? 'var(--color-bg-hover)' : undefined,\n transition: 'border-color 0.2s, background-color 0.2s',\n }}\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n Drop to add <strong>{dragWidgetType}</strong> widget\n </p>\n </div>\n )}\n\n <div\n ref={boardRef}\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-8\">\n <div className=\"w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface\">\n <svg className=\"w-8 h-8 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">No widgets yet</h3>\n <p className=\"text-sm text-text-secondary mb-4 text-center max-w-[300px]\">\n Add widgets to build your dashboard. Click the &quot;Add Widget&quot; button to get started.\n </p>\n {isEditMode && (\n <button\n type=\"button\"\n onClick={openWidgetPalette}\n className=\"\n inline-flex items-center gap-2\n px-4 py-2\n text-sm font-medium\n text-action-primary-fg\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-md\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 4v16m8-8H4\" />\n </svg>\n Add Widget\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <WidgetInspector isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || 'Dashboard'}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport type { Layout } from 'react-grid-layout';\nimport type { Widget, WidgetType } from '../../types';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useDashboardImageExport } from '../../hooks/useDashboardImageExport';\nimport { useBigConsole } from '../../context';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { LiveBoardBanner } from './LiveBoardBanner';\nimport { TemplateSetupBanner } from './TemplateSetupBanner';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetInspector } from '../builder';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\ninterface WidgetPlacement {\n position: { x: number; y: number; width: number; height: number };\n definition?: WidgetDefinition | null;\n}\n\ninterface PaletteDropData {\n type: WidgetType;\n name?: string;\n category?: WidgetDefinition['category'];\n defaultConfig?: Record<string, unknown>;\n defaultSize?: WidgetDefinition['defaultSize'];\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n // Inner board element (the grid surface) — rasterized for PNG/PDF board-pack export.\n const boardRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [widgetCreateError, setWidgetCreateError] = useState<string | null>(null);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const sidebarCollapsed = useDashboardStore((state) => state.sidebarCollapsed);\n const toggleSidebar = useDashboardStore((state) => state.toggleSidebar);\n const isPaletteOpen = !sidebarCollapsed;\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Keep react-grid-layout's explicit width in sync with the actual canvas.\n // A ResizeObserver is required because opening the docked inspector or widget\n // palette resizes this element without resizing the browser window.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateWidth = () => {\n setContainerWidth(container.offsetWidth);\n };\n\n updateWidth();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(updateWidth);\n observer.observe(container);\n return () => observer.disconnect();\n }\n\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Create with defaults, select the result, then configure it in the inspector.\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>, placement?: WidgetPlacement) => {\n setWidgetCreateError(null);\n const definition = placement?.definition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n let position: { x: number; y: number; width: number; height: number };\n if (placement?.position) {\n position = placement.position;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Preserve legacy binding fields when creating from imported/default data.\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n return;\n }\n\n setWidgetCreateError('Could not create the widget. Check the connection and try again.');\n return;\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n },\n [dashboardId, pageId, createWidgetApi, addWidgetToStore, selectWidget, widgetsArray]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - create immediately with defaults.\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const parsedData: unknown = JSON.parse(data);\n if (!parsedData || typeof parsedData !== 'object' || !('type' in parsedData)) return;\n const widgetData = parsedData as PaletteDropData;\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: definition.defaultSize.width,\n height: definition.defaultSize.height,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: 12 }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n void handleAddWidget(\n {\n type: widgetData.type,\n title: widgetData.name || definition.name,\n config: widgetData.defaultConfig || definition.defaultConfig,\n },\n { position, definition: droppedDefinition }\n );\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth, handleAddWidget]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n toggleSidebar();\n }, [toggleSidebar]);\n\n const openWidgetPalette = useCallback(() => {\n if (sidebarCollapsed) toggleSidebar();\n window.requestAnimationFrame(() => {\n document.querySelector<HTMLInputElement>('input[placeholder=\"Search widgets...\"]')?.focus();\n });\n }, [sidebarCollapsed, toggleSidebar]);\n\n // Board-pack export (PNG + PDF) — rasterizes the inner board element.\n const {\n exportPNG,\n exportPDF,\n isExporting: isExportingImage,\n } = useDashboardImageExport({ containerRef: boardRef, name: title || 'dashboard' });\n const handleExportImage = useCallback(() => {\n void exportPNG();\n }, [exportPNG]);\n const handleExportPdf = useCallback(() => {\n void exportPDF();\n }, [exportPDF]);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={openWidgetPalette}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onExportImage={handleExportImage}\n onExportPdf={handleExportPdf}\n isExportingImage={isExportingImage}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {widgetCreateError && (\n <div\n className=\"flex items-center justify-between gap-3 border-b border-status-error-border bg-status-error-bg px-4 py-2 text-sm text-status-error-text\"\n role=\"alert\"\n >\n <span>{widgetCreateError}</span>\n <button\n type=\"button\"\n className=\"font-medium underline underline-offset-2\"\n onClick={() => setWidgetCreateError(null)}\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Live wall-board freshness banner — renders only when the page has\n auto-refreshing widgets (e.g. the Live Bed Board) and not in edit mode. */}\n <TemplateSetupBanner widgets={widgetsArray} isEditMode={isEditMode} />\n <LiveBoardBanner widgets={widgetsArray} isEditMode={isEditMode} />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas.\n When the properties inspector is open we reserve equal space on the\n right so the (fixed, non-modal) panel docks beside the canvas instead\n of overlaying the selected widget. */}\n <div\n className=\"flex flex-1 min-w-0 overflow-hidden\"\n style={{\n marginRight: configPanelOpen ? 'var(--bc-inspector-width, 420px)' : undefined,\n transition: 'margin-right 0.2s ease',\n }}\n >\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={togglePalette}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/* Canvas area */}\n <div\n ref={containerRef}\n className=\"flex-1 min-w-0 overflow-auto bg-bg-sunken\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={{\n position: 'relative',\n border: isDraggingWidget ? '2px dashed var(--color-action-primary-bg)' : 'none',\n backgroundColor: isDraggingWidget ? 'var(--color-bg-hover)' : undefined,\n transition: 'border-color 0.2s, background-color 0.2s',\n }}\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n Drop to add <strong>{dragWidgetType}</strong> widget\n </p>\n </div>\n )}\n\n <div\n ref={boardRef}\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-8\">\n <div className=\"w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface\">\n <svg className=\"w-8 h-8 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">No widgets yet</h3>\n <p className=\"text-sm text-text-secondary mb-4 text-center max-w-[300px]\">\n Add widgets to build your dashboard. Click the &quot;Add Widget&quot; button to get started.\n </p>\n {isEditMode && (\n <button\n type=\"button\"\n onClick={openWidgetPalette}\n className=\"\n inline-flex items-center gap-2\n px-4 py-2\n text-sm font-medium\n text-action-primary-fg\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-md\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 4v16m8-8H4\" />\n </svg>\n Add Widget\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <WidgetInspector isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || 'Dashboard'}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA,IAAa,IAA4C,EAAK,SAAyB,EACrF,gBACA,WACA,UACA,aACA,YACA,cAAW,IACX,cACA,WAAQ,EAAE,EACV,kBACA,kBACA,mBACA,eACA,kBACA,qBACA,kBACA,kBAAc,IACd,mBAAe,IACf,wBAAoB,MACnB;CACD,IAAM,IAAe,EAAuB,KAAK,EAE3C,IAAW,EAAuB,KAAK,EACvC,CAAC,GAAgB,MAAqB,EAAS,KAAK,EACpD,CAAC,IAAmB,KAAwB,EAAS,GAAM,EAC3D,CAAC,GAAmB,KAAwB,EAAwB,KAAK,EACzE,CAAC,GAAkB,KAAuB,EAAS,GAAM,EACzD,CAAC,IAAgB,KAAqB,EAA4B,KAAK,EAGvE,IAAa,GAAmB,MAAU,EAAM,aAAa,OAAO,EAIpE,EAAE,gBAAa,kBAAe,iBAAc,GAAe,EAC3D,EAAE,sBAAkB,SAAS,OAA4B,EAAoB;EACjF;EACA;EACA;EACA,SAAS;EACV,CAAC,EAGI,EAAE,aAAa,OAAkB,EAAiB;EACtD;EACA;EACA;EACA,SAAS;EACV,CAAC,EACI,IAAY,GAAmB,MAAU,EAAM,UAAU,EACzD,IAAmB,GAAmB,MAAU,EAAM,iBAAiB,EACvE,IAAgB,GAAmB,MAAU,EAAM,cAAc,EACjE,IAAgB,CAAC,GACjB,IAAwB,GAAmB,MAAU,EAAM,sBAAsB,EAGjF,KAAqB,GACxB,MAA0C;AAEzC,IAAsB,EAAa;IAKrC,CAAC,EAAsB,CACxB,EAGK,EAAE,cAAc,MAAoB,EAAoB,GAAQ,GAAa,EAAE,WAAW,IAAM,CAAC,EAEjG,IAAa,GAAgB,MAAU,EAAM,QAAQ,EAErD,IAAe,SAAc;EACjC,IAAM,IAAa,MAAM,KAAK,EAAW,QAAQ,CAAC;AAElD,SADK,IACE,EAAW,QAAQ,MAAW,EAAO,WAAW,EAAO,GAD1C;IAEnB,CAAC,GAAY,EAAO,CAAC,EAClB,KAAmB,GAAgB,MAAU,EAAM,iBAAiB,EACpE,IAAe,GAAgB,MAAU,EAAM,aAAa,EAC5D,IAAmB,GAAgB,MAAU,EAAM,UAAU,EAC7D,IAAuB,GAAgB,MAAU,EAAM,qBAAqB,EAC5E,IAAkB,GAAgB,MAAU,EAAM,gBAAgB,EAClE,KAAmB,GAAgB,MAAU,EAAM,iBAAiB;AAK1E,UAAgB;EACd,IAAM,IAAY,EAAa;AAC/B,MAAI,CAAC,EAAW;EAEhB,IAAM,UAAoB;AACxB,MAAkB,EAAU,YAAY;;AAK1C,MAFA,GAAa,EAET,OAAO,iBAAmB,KAAa;GACzC,IAAM,IAAW,IAAI,eAAe,EAAY;AAEhD,UADA,EAAS,QAAQ,EAAU,QACd,EAAS,YAAY;;AAIpC,SADA,OAAO,iBAAiB,UAAU,EAAY,QACjC,OAAO,oBAAoB,UAAU,EAAY;IAC7D,EAAE,CAAC;CAGN,IAAM,IAAqB,GACxB,MAAqB;AACpB,IAAO,SAAS,MAAS;AACvB,KAAqB,EAAK,GAAG;IAC3B,GAAG,EAAK;IACR,GAAG,EAAK;IACR,OAAO,EAAK;IACZ,QAAQ,EAAK;IACd,CAAC;IACF;IAEJ,CAAC,EAAqB,CACvB,EAGK,KAAqB,GACxB,MAAqB;AACpB,IAAa,EAAS;IAExB,CAAC,EAAa,CACf,EAGK,IAAkB,EACtB,OAAO,GAA6B,MAAgC;AAClE,IAAqB,KAAK;EAC1B,IAAM,IAAa,GAAW,eAAe,EAAW,OAAO,EAAoB,EAAW,KAAK,GAAG,OAChG,IAAa;GACjB,OAAO,GAAY,aAAa,SAAS;GACzC,QAAQ,GAAY,aAAa,UAAU;GAC5C,EAEG;AACJ,MAAI,GAAW,SACb,KAAW,EAAU;OAChB;GAEL,IAAM,EAAE,MAAG,SAAM,GAA0B,GAAc,GAAY,EAAE,MAAM,IAAI,CAAC;AAClF,OAAW;IACT;IACA;IACA,OAAO,EAAW;IAClB,QAAQ,EAAW;IACpB;;EAIH,IAAM,IAAe;AAGrB,MAAI,GAAQ;GACV,IAAM,IAAS,MAAM,EAAgB;IACnC;IACA;IACA,MAAM,EAAW;IACjB,OAAO,EAAW,SAAS;IAC3B,aAAa,EAAW;IAExB,UAAU,EAAW;IACrB,UAAU,EAAW;IACrB,QAAQ,EAAW,UAAU,EAAE;IAC/B;IACA,iBAAiB,EAAW,mBAAmB,KAAA;IAE/C,YAAY,EAAa;IACzB,UAAU,EAAa;IACxB,CAAC;AAEF,OAAI,GAAQ;AACV,MAAa,EAAO,GAAG;AACvB;;AAGF,KAAqB,mEAAmE;AACxF;;EAIF,IAAM,IAAoB;GACxB,IAAI,OAAO,YAAY;GACvB;GACA,QAAQ,KAAU;GAClB,MAAM,EAAW;GACjB,OAAO,EAAW,SAAS;GAC3B,aAAa,EAAW;GAExB,UAAU,EAAW;GACrB,UAAU,EAAW;GACrB,iBAAiB,EAAW;GAC5B,QAAQ,EAAW,UAAU,EAAE;GAC/B;GACA,WAAW,EAAS;GACpB,WAAW,EAAS;GACpB,eAAe,EAAS;GACxB,gBAAgB,EAAS;GAEzB,YAAY,EAAa;GACzB,UAAU,EAAa;GACvB,cAAc,EAAE;GAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;AAGD,EADA,EAAiB,EAAU,EAC3B,EAAa,EAAU,GAAG;IAE5B;EAAC;EAAa;EAAQ;EAAiB;EAAkB;EAAc;EAAa,CACrF,EAGK,KAAyB,GAAa,MAAqB;AAE/D,EADA,EAAoB,GAAK,EACzB,EAAkB,EAAK;IACtB,EAAE,CAAC,EAGA,KAAuB,QAAkB;AAE7C,EADA,EAAoB,GAAM,EAC1B,EAAkB,KAAK;IACtB,EAAE,CAAC,EAGA,KAAmB,GACtB,MAAuB;AAGtB,EAFA,EAAE,gBAAgB,EAClB,EAAoB,GAAM,EAC1B,EAAkB,KAAK;AAEvB,MAAI;GACF,IAAM,IAAO,EAAE,aAAa,QAAQ,mBAAmB;AACvD,OAAI,CAAC,EAAM;GAEX,IAAM,IAAsB,KAAK,MAAM,EAAK;AAC5C,OAAI,CAAC,KAAc,OAAO,KAAe,YAAY,EAAE,UAAU,GAAa;GAC9E,IAAM,IAAa,GACb,IAAa,EAAoB,EAAW,KAAK,EAInD,IAA6C;AACjD,GAAI,EAAW,iBAAiB,EAAW,aACzC,IAAoB;IAClB,GAAG;IACH,MAAM,EAAW,QAAQ,EAAW;IACpC,UAAU,EAAW;IACrB,eAAe,EAAW;IAC1B,aAAa,EAAW,eAAe,EAAW;IACnD;GAIH,IAAM,IAAa,EAAa,SAAS,uBAAuB;AAChE,OAAI,CAAC,EAAY;GAGjB,IAAM,IAAa;IACjB,OAAO,EAAW,YAAY;IAC9B,QAAQ,EAAW,YAAY;IAChC,EAEK,EAAE,GAAG,GAAO,GAAG,MAAU,GAC7B,EAAE,SACF,EAAE,SACF,GACA,GACA,GACA,EAAE,MAAM,IAAI,CACb,EAEK,IAAW;IACf,GAAG;IACH,GAAG;IACH,OAAO,EAAW;IAClB,QAAQ,EAAW;IACpB;AAEI,KACH;IACE,MAAM,EAAW;IACjB,OAAO,EAAW,QAAQ,EAAW;IACrC,QAAQ,EAAW,iBAAiB,EAAW;IAChD,EACD;IAAE;IAAU,YAAY;IAAmB,CAC5C;WACM,GAAO;AACd,WAAQ,MAAM,iCAAiC,EAAM;;IAGzD,CAAC,GAAgB,EAAgB,CAClC,EAGK,KAAuB,GAAa,MAAuB;AAE/D,EADA,EAAE,gBAAgB,EAClB,EAAE,aAAa,aAAa;IAC3B,EAAE,CAAC,EAGA,KAA4B,GAC/B,GAAkB,MAAyC;EAC1D,IAAM,IAAa,KAAqB,EAAoB,EAAK;AACjE,IAAgB;GACd;GACA,OAAO,EAAW;GAClB,QAAQ,EAAW;GACpB,CAAC;IAEJ,CAAC,EAAgB,CAClB,EAGK,IAAgB,QAAkB;AACtC,KAAe;IACd,CAAC,EAAc,CAAC,EAEb,IAAoB,QAAkB;AAE1C,EADI,KAAkB,GAAe,EACrC,OAAO,4BAA4B;AACjC,YAAS,cAAgC,2CAAyC,EAAE,OAAO;IAC3F;IACD,CAAC,GAAkB,EAAc,CAAC,EAG/B,EACJ,cACA,cACA,aAAa,OACX,EAAwB;EAAE,cAAc;EAAU,MAAM,KAAS;EAAa,CAAC;AAQnF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,GAAD;IACe;IACN;IACP,aAAa;IACb,iBAAiB;IACF;IACN;IACD;IACE;IACA;IACV,eApBoB,QAAkB;AACrC,QAAW;OACf,CAAC,EAAU,CAAC;IAmBT,aAlBkB,QAAkB;AACnC,QAAW;OACf,CAAC,EAAU,CAAC;IAiBS;IAClB,eAAe,EAAqB,GAAK;IACzC,CAAA;GAED,KACC,kBAAC,OAAD;IACE,WAAU;IACV,MAAK;cAFP,CAIE,kBAAC,QAAD,EAAA,UAAO,GAAyB,CAAA,EAChC,kBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,eAAe,EAAqB,KAAK;eAC1C;KAEQ,CAAA,CACL;;GAKR,kBAAC,IAAD;IAAqB,SAAS;IAA0B;IAAc,CAAA;GACtE,kBAAC,IAAD;IAAiB,SAAS;IAA0B;IAAc,CAAA;GAGjE,MAAe,kBAAC,GAAD;IAA+B;IAAa,gBAAgB;IAAoB,aAAA;IAAc,CAAA;GAG7G,OAAiB,KAAc,EAAM,SAAS,MAC7C,kBAAC,IAAD;IACS;IACP,cAAc,MAAgB,EAAM,IAAI;IACxC,UAAU;IACV,cAAc,aAAuB;IACrC,gBACE,KACK,MAAY;AAGX,OADuB,EAAQ,KAAK,MAAO,EAAM,MAAM,MAAM,EAAE,OAAO,EAAG,CAAE,CAAC,OAAO,QAAQ,CAC7D;QAEhC,KAAA;IAEN,cAAc;IACA;IACG;IACH;IACd,CAAA;GAOJ,kBAAC,OAAD;IACE,WAAU;IACV,OAAO;KACL,aAAa,IAAkB,qCAAqC,KAAA;KACpE,YAAY;KACb;cALH,CAQG,KACC,kBAAC,IAAD;KACE,QAAQ;KACR,SAAS;KACT,gBAAgB;KAChB,aAAa;KACb,WAAW;KACO;KACO;KACzB,aAAa;KACb,CAAA,EAIJ,kBAAC,OAAD;KACE,KAAK;KACL,WAAU;KACV,QAAQ;KACR,YAAY;KACZ,eAAe;AACb,MAAI,KACF,EAAa,KAAK;;KAGtB,OAAO;MACL,UAAU;MACV,QAAQ,IAAmB,8CAA8C;MACzE,iBAAiB,IAAmB,0BAA0B,KAAA;MAC9D,YAAY;MACb;eAfH,CAkBG,KACC,kBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,KAAK;OACL,MAAM;OACN,WAAW;OACX,SAAS;OACT,iBAAiB;OACjB,cAAc;OACd,WAAW;OACX,WAAW;OACX,eAAe;OACf,QAAQ;OACT;gBAED,kBAAC,KAAD;OAAG,OAAO;QAAE,UAAU;QAA4B,OAAO;QAA6B,QAAQ;QAAG;iBAAjG;QAAmG;QACrF,kBAAC,UAAD,EAAA,UAAS,IAAwB,CAAA;;QAC3C;;MACA,CAAA,EAGR,kBAAC,OAAD;MACE,KAAK;MACL,WAAU;MACV,OAAO;OACL,WAAW,SAAS,IAAY,IAAI;OACpC,OAAO,GAAG,OAAO,IAAY,KAAK;OACnC;gBAEA,EAAa,SAAS,IACrB,kBAAC,GAAD;OACE,SAAS;OACT,gBAAgB,MAAa;iBAI7B,kBAAC,GAAD;QACE,SAAS;QACG;QACZ,OAAO;QACP,gBAAgB;QAChB,gBAAgB;QACE;QAClB,CAAA;OACkB,CAAA,GAEtB,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;UAA8B,MAAK;UAAO,QAAO;UAAe,SAAQ;oBACrF,kBAAC,QAAD;WACE,eAAc;WACd,gBAAe;WACf,aAAa;WACb,GAAE;WACF,CAAA;UACE,CAAA;SACF,CAAA;QACN,kBAAC,MAAD;SAAI,WAAU;mBAA6C;SAAmB,CAAA;QAC9E,kBAAC,KAAD;SAAG,WAAU;mBAA6D;SAEtE,CAAA;QACH,KACC,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAHZ,CAcE,kBAAC,OAAD;UAAK,WAAU;UAAU,MAAK;UAAO,QAAO;UAAe,SAAQ;oBACjE,kBAAC,QAAD;WAAM,eAAc;WAAQ,gBAAe;WAAQ,aAAa;WAAG,GAAE;WAAmB,CAAA;UACpF,CAAA,EAAA,aAEC;;QAEP;;MAEJ,CAAA,CACF;OACF;;GAGN,kBAAC,IAAD;IAAiB,QAAQ;IAAiB,SAAS;IAAoB,CAAA;GAGvE,kBAAC,IAAD;IACe;IACb,eAAe,KAAS;IACxB,QAAQ;IACR,eAAe,EAAqB,GAAM;IAC1C,CAAA;GACE;;EAER"}
@@ -0,0 +1,82 @@
1
+ import { memo as e, useMemo as t } from "react";
2
+ import { DatabaseZap as n } from "lucide-react";
3
+ import { Button as r } from "@burdenoff/fe-libs/ui";
4
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
5
+ //#region src/bigconsole/components/dashboard/TemplateSetupBanner.tsx
6
+ var o = "templateDataSinkSlot";
7
+ function s(e) {
8
+ if (e.dataSinkId) return null;
9
+ let t = e.metadata?.[o];
10
+ return typeof t == "string" && t.length > 0 ? t : null;
11
+ }
12
+ var c = e(function({ widgets: e, isEditMode: o = !1, onConnectData: c }) {
13
+ let { unconnectedCount: l, slots: u } = t(() => {
14
+ let t = /* @__PURE__ */ new Set(), n = 0;
15
+ for (let r of e) {
16
+ let e = s(r);
17
+ e && (n += 1, t.add(e));
18
+ }
19
+ return {
20
+ unconnectedCount: n,
21
+ slots: Array.from(t)
22
+ };
23
+ }, [e]);
24
+ if (o || l === 0) return null;
25
+ let d = u.length === 1 ? "data source" : "data sources";
26
+ return /* @__PURE__ */ a("div", {
27
+ role: "status",
28
+ style: {
29
+ display: "flex",
30
+ alignItems: "center",
31
+ gap: "var(--space-sm)",
32
+ padding: "var(--space-sm) var(--space-md)",
33
+ marginBottom: "var(--space-sm)",
34
+ borderRadius: "var(--radius-md)",
35
+ border: "1px solid var(--color-border-info)",
36
+ background: "var(--color-surface-info-subtle)",
37
+ color: "var(--color-text-info)"
38
+ },
39
+ children: [
40
+ /* @__PURE__ */ i(n, {
41
+ style: {
42
+ height: "var(--size-icon-sm)",
43
+ width: "var(--size-icon-sm)",
44
+ flexShrink: 0
45
+ },
46
+ "aria-hidden": "true"
47
+ }),
48
+ /* @__PURE__ */ a("div", {
49
+ style: {
50
+ flex: 1,
51
+ minWidth: 0
52
+ },
53
+ children: [/* @__PURE__ */ i("div", {
54
+ style: { fontWeight: "var(--font-weight-medium)" },
55
+ children: "This dashboard came from a template"
56
+ }), /* @__PURE__ */ a("div", {
57
+ style: { fontSize: "var(--font-size-sm)" },
58
+ children: [
59
+ l,
60
+ " ",
61
+ l === 1 ? "widget needs" : "widgets need",
62
+ " connecting to your data — ",
63
+ u.length,
64
+ " ",
65
+ d,
66
+ " to set up."
67
+ ]
68
+ })]
69
+ }),
70
+ c ? /* @__PURE__ */ i(r, {
71
+ size: "sm",
72
+ onClick: () => c(u),
73
+ style: { flexShrink: 0 },
74
+ children: "Connect data"
75
+ }) : null
76
+ ]
77
+ });
78
+ });
79
+ //#endregion
80
+ export { c as default };
81
+
82
+ //# sourceMappingURL=TemplateSetupBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateSetupBanner.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/TemplateSetupBanner.tsx"],"sourcesContent":["/**\n * TemplateSetupBanner\n *\n * Shown on a board installed from a store template that still has widgets\n * without a data source.\n *\n * WHY THIS EXISTS:\n * Template installs deliberately do not require every data-sink slot to be\n * bound up front — a brand-new workspace has no sinks at all, so a hard\n * requirement would make the template gallery unusable for exactly the users\n * it exists to serve. The cost of that choice is that the board renders\n * immediately with some widgets holding no data.\n *\n * A workflow can get away with the equivalent (FluidGrids just toasts \"go\n * configure your credential\") because nothing runs until you press Test. A\n * dashboard renders the instant you open it, so an unconnected widget is\n * visually indistinguishable from a crashed one. This banner is what\n * reframes the screen from \"broken\" to \"setup in progress\", and it does more\n * work for that than any per-widget treatment.\n *\n * It derives its state from the widgets themselves — each widget installed from\n * a template carries its originating slot in `metadata.templateDataSinkSlot` —\n * so it needs no extra query and lights up for any such board.\n *\n * Semantic status tokens only, no raw colors.\n *\n * @module bigconsole/components/dashboard/TemplateSetupBanner\n */\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { DatabaseZap } from 'lucide-react';\nimport { type FC, memo, useMemo } from 'react';\n\nimport type { Widget } from '../../types';\n\n/** Metadata key written by the backend template installer. */\nconst WIDGET_TEMPLATE_SLOT_KEY = 'templateDataSinkSlot';\n\nexport interface TemplateSetupBannerProps {\n widgets: Widget[];\n /** Hidden while editing: the builder has its own binding affordances. */\n isEditMode?: boolean;\n /** Opens the guided connect flow. Omit to render the banner without a CTA. */\n onConnectData?: (slots: string[]) => void;\n}\n\n/** A widget that came from a template and still has no sink bound. */\nfunction needsData(widget: Widget): string | null {\n if (widget.dataSinkId) return null;\n const slot = widget.metadata?.[WIDGET_TEMPLATE_SLOT_KEY];\n return typeof slot === 'string' && slot.length > 0 ? slot : null;\n}\n\nexport const TemplateSetupBanner: FC<TemplateSetupBannerProps> = memo(function TemplateSetupBanner({\n widgets,\n isEditMode = false,\n onConnectData,\n}) {\n const { unconnectedCount, slots } = useMemo(() => {\n const found = new Set<string>();\n let count = 0;\n for (const widget of widgets) {\n const slot = needsData(widget);\n if (!slot) continue;\n count += 1;\n found.add(slot);\n }\n return { unconnectedCount: count, slots: Array.from(found) };\n }, [widgets]);\n\n if (isEditMode || unconnectedCount === 0) return null;\n\n const sourceWord = slots.length === 1 ? 'data source' : 'data sources';\n const widgetWord = unconnectedCount === 1 ? 'widget needs' : 'widgets need';\n\n return (\n <div\n role=\"status\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-sm)',\n padding: 'var(--space-sm) var(--space-md)',\n marginBottom: 'var(--space-sm)',\n borderRadius: 'var(--radius-md)',\n border: '1px solid var(--color-border-info)',\n background: 'var(--color-surface-info-subtle)',\n color: 'var(--color-text-info)',\n }}\n >\n <DatabaseZap\n style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)', flexShrink: 0 }}\n aria-hidden=\"true\"\n />\n\n <div style={{ flex: 1, minWidth: 0 }}>\n <div style={{ fontWeight: 'var(--font-weight-medium)' }}>This dashboard came from a template</div>\n <div style={{ fontSize: 'var(--font-size-sm)' }}>\n {unconnectedCount} {widgetWord} connecting to your data — {slots.length} {sourceWord} to set up.\n </div>\n </div>\n\n {onConnectData ? (\n <Button size=\"sm\" onClick={() => onConnectData(slots)} style={{ flexShrink: 0 }}>\n Connect data\n </Button>\n ) : null}\n </div>\n );\n});\n\nexport default TemplateSetupBanner;\n"],"mappings":";;;;;AAmCA,IAAM,IAA2B;AAWjC,SAAS,EAAU,GAA+B;AAChD,KAAI,EAAO,WAAY,QAAO;CAC9B,IAAM,IAAO,EAAO,WAAW;AAC/B,QAAO,OAAO,KAAS,YAAY,EAAK,SAAS,IAAI,IAAO;;AAG9D,IAAa,IAAoD,EAAK,SAA6B,EACjG,YACA,gBAAa,IACb,oBACC;CACD,IAAM,EAAE,qBAAkB,aAAU,QAAc;EAChD,IAAM,oBAAQ,IAAI,KAAa,EAC3B,IAAQ;AACZ,OAAK,IAAM,KAAU,GAAS;GAC5B,IAAM,IAAO,EAAU,EAAO;AACzB,SACL,KAAS,GACT,EAAM,IAAI,EAAK;;AAEjB,SAAO;GAAE,kBAAkB;GAAO,OAAO,MAAM,KAAK,EAAM;GAAE;IAC3D,CAAC,EAAQ,CAAC;AAEb,KAAI,KAAc,MAAqB,EAAG,QAAO;CAEjD,IAAM,IAAa,EAAM,WAAW,IAAI,gBAAgB;AAGxD,QACE,kBAAC,OAAD;EACE,MAAK;EACL,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK;GACL,SAAS;GACT,cAAc;GACd,cAAc;GACd,QAAQ;GACR,YAAY;GACZ,OAAO;GACR;YAZH;GAcE,kBAAC,GAAD;IACE,OAAO;KAAE,QAAQ;KAAuB,OAAO;KAAuB,YAAY;KAAG;IACrF,eAAY;IACZ,CAAA;GAEF,kBAAC,OAAD;IAAK,OAAO;KAAE,MAAM;KAAG,UAAU;KAAG;cAApC,CACE,kBAAC,OAAD;KAAK,OAAO,EAAE,YAAY,6BAA6B;eAAE;KAAyC,CAAA,EAClG,kBAAC,OAAD;KAAK,OAAO,EAAE,UAAU,uBAAuB;eAA/C;MACG;MAAiB;MAzBP,MAAqB,IAAI,iBAAiB;MAyBtB;MAA4B,EAAM;MAAO;MAAE;MAAW;MACjF;OACF;;GAEL,IACC,kBAAC,GAAD;IAAQ,MAAK;IAAK,eAAe,EAAc,EAAM;IAAE,OAAO,EAAE,YAAY,GAAG;cAAE;IAExE,CAAA,GACP;GACA;;EAER"}
@@ -0,0 +1,25 @@
1
+ import { Component as e } from "react";
2
+ import { jsxs as t } from "react/jsx-runtime";
3
+ //#region src/bigconsole/components/embed/EmbedErrorBoundary.tsx
4
+ var n = class extends e {
5
+ state = { hasError: !1 };
6
+ static getDerivedStateFromError() {
7
+ return { hasError: !0 };
8
+ }
9
+ componentDidCatch(e, t) {}
10
+ render() {
11
+ return this.state.hasError ? /* @__PURE__ */ t("div", {
12
+ role: "alert",
13
+ className: "flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary",
14
+ children: [
15
+ "This ",
16
+ this.props.label ?? "content",
17
+ " could not be displayed."
18
+ ]
19
+ }) : this.props.children;
20
+ }
21
+ };
22
+ //#endregion
23
+ export { n as EmbedErrorBoundary };
24
+
25
+ //# sourceMappingURL=EmbedErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmbedErrorBoundary.js","names":[],"sources":["../../../../src/bigconsole/components/embed/EmbedErrorBoundary.tsx"],"sourcesContent":["/**\n * Customer-facing error boundary for embedded widgets/dashboards (BOFF-2986).\n *\n * Renders a neutral, safe message and NEVER surfaces internal details (stack\n * traces, ids, endpoints, config). One failing widget must not tear down the\n * whole dashboard, so this wraps each widget individually as well as the page.\n */\n\nimport { Component, type ErrorInfo, type ReactNode } from 'react';\n\ninterface Props {\n readonly children: ReactNode;\n /** Short, non-sensitive label for what failed (e.g. \"widget\"). */\n readonly label?: string;\n}\n\ninterface State {\n readonly hasError: boolean;\n}\n\nexport class EmbedErrorBoundary extends Component<Props, State> {\n state: State = { hasError: false };\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n componentDidCatch(_error: Error, _info: ErrorInfo): void {\n // Intentionally no external reporting from the anonymous embed context — the\n // safe render below is the only user-visible outcome.\n }\n\n render(): ReactNode {\n if (this.state.hasError) {\n return (\n <div\n role=\"alert\"\n className=\"flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary\"\n >\n This {this.props.label ?? 'content'} could not be displayed.\n </div>\n );\n }\n return this.props.children;\n }\n}\n"],"mappings":";;;AAoBA,IAAa,IAAb,cAAwC,EAAwB;CAC9D,QAAe,EAAE,UAAU,IAAO;CAElC,OAAO,2BAAkC;AACvC,SAAO,EAAE,UAAU,IAAM;;CAG3B,kBAAkB,GAAe,GAAwB;CAKzD,SAAoB;AAWlB,SAVI,KAAK,MAAM,WAEX,kBAAC,OAAD;GACE,MAAK;GACL,WAAU;aAFZ;IAGC;IACO,KAAK,MAAM,SAAS;IAAU;IAChC;OAGH,KAAK,MAAM"}
@@ -0,0 +1,36 @@
1
+ import { EMBED_WIDGET_COVERAGE as e } from "./renderer-manifest.js";
2
+ import { EmbedErrorBoundary as t } from "./EmbedErrorBoundary.js";
3
+ import { MetricCardEmbed as n } from "./widgets/MetricCardEmbed.js";
4
+ import { KpiComparisonEmbed as r } from "./widgets/KpiComparisonEmbed.js";
5
+ import { TableEmbed as i } from "./widgets/TableEmbed.js";
6
+ import { ListEmbed as a } from "./widgets/ListEmbed.js";
7
+ import { TextEmbed as o } from "./widgets/TextEmbed.js";
8
+ import { jsx as s } from "react/jsx-runtime";
9
+ //#region src/bigconsole/components/embed/EmbedWidgetRenderer.tsx
10
+ var c = {
11
+ metric_card: n,
12
+ kpi_card_comparison: r,
13
+ table: i,
14
+ list: a,
15
+ text: o
16
+ };
17
+ function l() {
18
+ return Object.keys(c);
19
+ }
20
+ function u({ title: e }) {
21
+ return /* @__PURE__ */ s("div", {
22
+ className: "flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary",
23
+ children: e ? `“${e}” is not available in this embed.` : "This widget is not available in this embed."
24
+ });
25
+ }
26
+ function d(n) {
27
+ let { widget: r } = n, i = e[r.type]?.support === "implemented" ? c[r.type] : void 0;
28
+ return i ? /* @__PURE__ */ s(t, {
29
+ label: "widget",
30
+ children: /* @__PURE__ */ s(i, { ...n })
31
+ }) : /* @__PURE__ */ s(u, { title: r.title });
32
+ }
33
+ //#endregion
34
+ export { d as EmbedWidgetRenderer, l as getImplementedRendererTypes };
35
+
36
+ //# sourceMappingURL=EmbedWidgetRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmbedWidgetRenderer.js","names":[],"sources":["../../../../src/bigconsole/components/embed/EmbedWidgetRenderer.tsx"],"sourcesContent":["/**\n * Strict embed widget dispatcher (BOFF-2986).\n *\n * Maps a sanitized widget DTO to its embed renderer via an EXACT registry.\n * There is NO silent fallback (unlike the authenticated WidgetRendererFactory):\n * a type that is not `implemented` in the coverage manifest renders a neutral\n * \"not available\" placeholder, never a guessed or partial widget. Each widget is\n * wrapped in its own error boundary so one failure can't take down the page.\n */\n\nimport type { ComponentType } from 'react';\nimport type { WidgetType } from '../../types';\nimport type { EmbedWidgetRenderProps } from './types';\nimport { EMBED_WIDGET_COVERAGE } from './renderer-manifest';\nimport { EmbedErrorBoundary } from './EmbedErrorBoundary';\nimport { MetricCardEmbed } from './widgets/MetricCardEmbed';\nimport { KpiComparisonEmbed } from './widgets/KpiComparisonEmbed';\nimport { TableEmbed } from './widgets/TableEmbed';\nimport { ListEmbed } from './widgets/ListEmbed';\nimport { TextEmbed } from './widgets/TextEmbed';\n\n/**\n * Registry of implemented renderers. Every key here MUST be marked\n * `implemented` in EMBED_WIDGET_COVERAGE, and vice-versa — the completeness test\n * asserts the two stay in lockstep, so a renderer can never be silently missing\n * for a type the manifest claims to support.\n */\nconst IMPLEMENTED_RENDERERS: Partial<Record<WidgetType, ComponentType<EmbedWidgetRenderProps>>> = {\n metric_card: MetricCardEmbed,\n kpi_card_comparison: KpiComparisonEmbed,\n table: TableEmbed,\n list: ListEmbed,\n text: TextEmbed,\n};\n\nexport function getImplementedRendererTypes(): WidgetType[] {\n return Object.keys(IMPLEMENTED_RENDERERS) as WidgetType[];\n}\n\nfunction UnavailableWidget({ title }: { title?: string }) {\n return (\n <div className=\"flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary\">\n {title ? `“${title}” is not available in this embed.` : 'This widget is not available in this embed.'}\n </div>\n );\n}\n\nexport function EmbedWidgetRenderer(props: EmbedWidgetRenderProps) {\n const { widget } = props;\n const coverage = EMBED_WIDGET_COVERAGE[widget.type];\n const Renderer = coverage?.support === 'implemented' ? IMPLEMENTED_RENDERERS[widget.type] : undefined;\n\n if (!Renderer) {\n return <UnavailableWidget title={widget.title} />;\n }\n\n return (\n <EmbedErrorBoundary label=\"widget\">\n <Renderer {...props} />\n </EmbedErrorBoundary>\n );\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAM,IAA4F;CAChG,aAAa;CACb,qBAAqB;CACrB,OAAO;CACP,MAAM;CACN,MAAM;CACP;AAED,SAAgB,IAA4C;AAC1D,QAAO,OAAO,KAAK,EAAsB;;AAG3C,SAAS,EAAkB,EAAE,YAA6B;AACxD,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,IAAQ,IAAI,EAAM,qCAAqC;EACpD,CAAA;;AAIV,SAAgB,EAAoB,GAA+B;CACjE,IAAM,EAAE,cAAW,GAEb,IADW,EAAsB,EAAO,OACnB,YAAY,gBAAgB,EAAsB,EAAO,QAAQ,KAAA;AAM5F,QAJK,IAKH,kBAAC,GAAD;EAAoB,OAAM;YACxB,kBAAC,GAAD,EAAU,GAAI,GAAS,CAAA;EACJ,CAAA,GANd,kBAAC,GAAD,EAAmB,OAAO,EAAO,OAAS,CAAA"}