@inkindcards/semantic-layer 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -142,9 +142,11 @@ interface WidgetConfig {
142
142
  grain: TimeGrain | null;
143
143
  comparisonMetric?: SemanticField;
144
144
  }
145
+ type TimePreset = "12m" | "6m" | "3m" | "1m" | "1w" | "1d";
145
146
  interface SharedFilter {
146
147
  dimension: SemanticField;
147
148
  label: string;
149
+ timePreset?: TimePreset | null;
148
150
  }
149
151
  interface ReportConfig {
150
152
  dashboardTitle: string;
@@ -153,7 +155,8 @@ interface ReportConfig {
153
155
  sharedFilters: SharedFilter[];
154
156
  }
155
157
  declare function generateReportPrompt(config: ReportConfig): string;
158
+ declare function isRunningInIframe(): boolean;
156
159
  declare function canOpenInLovable(prompt: string): boolean;
157
160
  declare function openInLovable(prompt: string): void;
158
161
 
159
- export { AdminDashboard, type AdminDashboardProps, type ChartAnalysis, type DashboardLayout, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type DimensionMapping, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, type MigrationConfig, type ReportConfig, ResultsTable, type ResultsTableProps, type SharedFilter, type WidgetConfig, type WidgetType, analyzeChartData, canOpenInLovable, generateMigrationPrompt, generateReportPrompt, matchField, matchFields, openInLovable };
162
+ export { AdminDashboard, type AdminDashboardProps, type ChartAnalysis, type DashboardLayout, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type DimensionMapping, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, type MigrationConfig, type ReportConfig, ResultsTable, type ResultsTableProps, type SharedFilter, type TimePreset, type WidgetConfig, type WidgetType, analyzeChartData, canOpenInLovable, generateMigrationPrompt, generateReportPrompt, isRunningInIframe, matchField, matchFields, openInLovable };
@@ -142,9 +142,11 @@ interface WidgetConfig {
142
142
  grain: TimeGrain | null;
143
143
  comparisonMetric?: SemanticField;
144
144
  }
145
+ type TimePreset = "12m" | "6m" | "3m" | "1m" | "1w" | "1d";
145
146
  interface SharedFilter {
146
147
  dimension: SemanticField;
147
148
  label: string;
149
+ timePreset?: TimePreset | null;
148
150
  }
149
151
  interface ReportConfig {
150
152
  dashboardTitle: string;
@@ -153,7 +155,8 @@ interface ReportConfig {
153
155
  sharedFilters: SharedFilter[];
154
156
  }
155
157
  declare function generateReportPrompt(config: ReportConfig): string;
158
+ declare function isRunningInIframe(): boolean;
156
159
  declare function canOpenInLovable(prompt: string): boolean;
157
160
  declare function openInLovable(prompt: string): void;
158
161
 
159
- export { AdminDashboard, type AdminDashboardProps, type ChartAnalysis, type DashboardLayout, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type DimensionMapping, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, type MigrationConfig, type ReportConfig, ResultsTable, type ResultsTableProps, type SharedFilter, type WidgetConfig, type WidgetType, analyzeChartData, canOpenInLovable, generateMigrationPrompt, generateReportPrompt, matchField, matchFields, openInLovable };
162
+ export { AdminDashboard, type AdminDashboardProps, type ChartAnalysis, type DashboardLayout, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type DimensionMapping, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, type MigrationConfig, type ReportConfig, ResultsTable, type ResultsTableProps, type SharedFilter, type TimePreset, type WidgetConfig, type WidgetType, analyzeChartData, canOpenInLovable, generateMigrationPrompt, generateReportPrompt, isRunningInIframe, matchField, matchFields, openInLovable };
@@ -1081,6 +1081,14 @@ function generateMigrationPrompt(config) {
1081
1081
  }
1082
1082
 
1083
1083
  // src/components/report-prompt-generator.ts
1084
+ var TIME_PRESET_LABELS = {
1085
+ "12m": "12 months",
1086
+ "6m": "6 months",
1087
+ "3m": "3 months",
1088
+ "1m": "1 month",
1089
+ "1w": "1 week",
1090
+ "1d": "1 day"
1091
+ };
1084
1092
  var PALETTE = [
1085
1093
  "#3b82f6",
1086
1094
  "#10b981",
@@ -1138,8 +1146,19 @@ function generateReportPrompt(config) {
1138
1146
  parts.push("};");
1139
1147
  parts.push("```");
1140
1148
  parts.push("");
1141
- parts.push("For each filter dropdown, query the distinct values of the dimension using useSemanticQuery with that dimension as a groupBy and any single metric, then populate the dropdown options from the returned rows.");
1142
- parts.push("");
1149
+ const timeFilters = sharedFilters.filter((f) => f.dimension.type === "time_dimension" && f.timePreset);
1150
+ const dimFilters = sharedFilters.filter((f) => f.dimension.type !== "time_dimension" || !f.timePreset);
1151
+ if (timeFilters.length > 0) {
1152
+ parts.push("For time filters, compute a date range relative to today:");
1153
+ for (const f of timeFilters) {
1154
+ parts.push(` - "${f.dimension.name}": default to the last ${TIME_PRESET_LABELS[f.timePreset]}. Compute a start date (e.g. \`new Date(); startDate.setMonth(startDate.getMonth() - N)\`) and pass it as a filter value. Include preset buttons (${Object.values(TIME_PRESET_LABELS).join(", ")}) so the user can switch ranges.`);
1155
+ }
1156
+ parts.push("");
1157
+ }
1158
+ if (dimFilters.length > 0) {
1159
+ parts.push("For each dimension filter dropdown, query the distinct values of the dimension using useSemanticQuery with that dimension as a groupBy and any single metric, then populate the dropdown options from the returned rows.");
1160
+ parts.push("");
1161
+ }
1143
1162
  }
1144
1163
  parts.push("## Layout");
1145
1164
  parts.push("");
@@ -1294,6 +1313,13 @@ function sanitize(name) {
1294
1313
  return name.replace(/[^a-zA-Z0-9_]/g, "_");
1295
1314
  }
1296
1315
  var LOVABLE_CHAR_LIMIT = 5e4;
1316
+ function isRunningInIframe() {
1317
+ try {
1318
+ return window.self !== window.top;
1319
+ } catch {
1320
+ return true;
1321
+ }
1322
+ }
1297
1323
  function canOpenInLovable(prompt) {
1298
1324
  return prompt.length <= LOVABLE_CHAR_LIMIT;
1299
1325
  }
@@ -1439,16 +1465,48 @@ function UnifiedModal({
1439
1465
  entry,
1440
1466
  onClose
1441
1467
  }) {
1468
+ const [dragPos, setDragPos] = useState(null);
1469
+ const dragRef = useRef(null);
1470
+ const modalRef = useRef(null);
1471
+ const handleHeaderMouseDown = useCallback((e) => {
1472
+ if (e.target.closest("button")) return;
1473
+ e.preventDefault();
1474
+ const modal = modalRef.current;
1475
+ if (!modal) return;
1476
+ const rect = modal.getBoundingClientRect();
1477
+ dragRef.current = { startX: e.clientX, startY: e.clientY, origX: rect.left, origY: rect.top };
1478
+ const onMove = (ev) => {
1479
+ if (!dragRef.current) return;
1480
+ const dx = ev.clientX - dragRef.current.startX;
1481
+ const dy = ev.clientY - dragRef.current.startY;
1482
+ setDragPos({ x: dragRef.current.origX + dx, y: dragRef.current.origY + dy });
1483
+ };
1484
+ const onUp = () => {
1485
+ dragRef.current = null;
1486
+ document.removeEventListener("mousemove", onMove);
1487
+ document.removeEventListener("mouseup", onUp);
1488
+ };
1489
+ document.addEventListener("mousemove", onMove);
1490
+ document.addEventListener("mouseup", onUp);
1491
+ }, []);
1492
+ const positionStyle = dragPos ? { position: "fixed", top: dragPos.y, left: dragPos.x, transform: "none" } : { position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
1442
1493
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1443
1494
  /* @__PURE__ */ jsx("div", { onClick: onClose, style: backdropStyle }),
1444
- /* @__PURE__ */ jsxs("div", { style: modalStyle, children: [
1445
- /* @__PURE__ */ jsxs("div", { style: modalHeaderStyle, children: [
1446
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 0 }, children: [
1447
- /* @__PURE__ */ jsx(TabButton, { label: "Migrate", active: tab === "migrate", onClick: () => onTabChange("migrate") }),
1448
- /* @__PURE__ */ jsx(TabButton, { label: "Build", active: tab === "build", onClick: () => onTabChange("build") })
1449
- ] }),
1450
- /* @__PURE__ */ jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
1451
- ] }),
1495
+ /* @__PURE__ */ jsxs("div", { ref: modalRef, style: { ...modalStyle, ...positionStyle }, children: [
1496
+ /* @__PURE__ */ jsxs(
1497
+ "div",
1498
+ {
1499
+ onMouseDown: handleHeaderMouseDown,
1500
+ style: { ...modalHeaderStyle, cursor: dragRef.current ? "grabbing" : "grab" },
1501
+ children: [
1502
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 0 }, children: [
1503
+ /* @__PURE__ */ jsx(TabButton, { label: "Migrate", active: tab === "migrate", onClick: () => onTabChange("migrate") }),
1504
+ /* @__PURE__ */ jsx(TabButton, { label: "Build", active: tab === "build", onClick: () => onTabChange("build") })
1505
+ ] }),
1506
+ /* @__PURE__ */ jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
1507
+ ]
1508
+ }
1509
+ ),
1452
1510
  tab === "migrate" ? /* @__PURE__ */ jsx(MigrateTab, { entry, onClose }) : /* @__PURE__ */ jsx(BuildTab, {})
1453
1511
  ] })
1454
1512
  ] });
@@ -1681,7 +1739,14 @@ var WIDGET_TYPES = [
1681
1739
  function BuildTab() {
1682
1740
  const { fields: catalog, isLoading: catalogLoading } = useMetrics();
1683
1741
  const allMetrics = useMemo(() => catalog.filter((f) => f.type === "metric"), [catalog]);
1684
- const allDimensions = useMemo(() => catalog.filter((f) => f.type === "dimension" || f.type === "time_dimension"), [catalog]);
1742
+ const allDimensions = useMemo(() => {
1743
+ const dims = catalog.filter((f) => f.type === "dimension" || f.type === "time_dimension");
1744
+ return dims.sort((a, b) => {
1745
+ if (a.name === "metric_time") return -1;
1746
+ if (b.name === "metric_time") return 1;
1747
+ return 0;
1748
+ });
1749
+ }, [catalog]);
1685
1750
  const [dashboardTitle, setDashboardTitle] = useState("Dashboard");
1686
1751
  const [layout, setLayout] = useState("dashboard");
1687
1752
  const [widgets, setWidgets] = useState([]);
@@ -1690,29 +1755,74 @@ function BuildTab() {
1690
1755
  const [copied, setCopied] = useState(false);
1691
1756
  const [lovableSent, setLovableSent] = useState(false);
1692
1757
  const [previewOpen, setPreviewOpen] = useState(false);
1758
+ const [catalogWidth, setCatalogWidth] = useState(220);
1759
+ const catalogDragRef = useRef(null);
1760
+ const inIframe = useMemo(() => isRunningInIframe(), []);
1761
+ const handleCatalogDragStart = useCallback((e) => {
1762
+ e.preventDefault();
1763
+ catalogDragRef.current = { startX: e.clientX, startW: catalogWidth };
1764
+ const onMove = (ev) => {
1765
+ if (!catalogDragRef.current) return;
1766
+ const delta = ev.clientX - catalogDragRef.current.startX;
1767
+ setCatalogWidth(Math.max(160, Math.min(400, catalogDragRef.current.startW + delta)));
1768
+ };
1769
+ const onUp = () => {
1770
+ catalogDragRef.current = null;
1771
+ document.removeEventListener("mousemove", onMove);
1772
+ document.removeEventListener("mouseup", onUp);
1773
+ };
1774
+ document.addEventListener("mousemove", onMove);
1775
+ document.addEventListener("mouseup", onUp);
1776
+ }, [catalogWidth]);
1693
1777
  const addWidget = useCallback((type) => {
1778
+ const isTimeSeries = type === "line" || type === "area" || type === "kpi";
1779
+ const metricTime = allDimensions.find((d) => d.name === "metric_time");
1694
1780
  setWidgets((prev) => [...prev, {
1695
1781
  id: nextWidgetId(),
1696
1782
  title: `${WIDGET_TYPES.find((t) => t.type === type)?.label ?? "Widget"} ${prev.length + 1}`,
1697
1783
  type,
1698
1784
  metrics: [],
1699
- groupBy: [],
1700
- grain: null
1785
+ groupBy: isTimeSeries && metricTime ? [metricTime] : [],
1786
+ grain: isTimeSeries && metricTime ? "month" : null
1701
1787
  }]);
1702
- }, []);
1788
+ }, [allDimensions]);
1703
1789
  const removeWidget = useCallback((id) => {
1704
1790
  setWidgets((prev) => prev.filter((w) => w.id !== id));
1705
1791
  }, []);
1706
1792
  const updateWidget = useCallback((id, patch) => {
1707
1793
  setWidgets((prev) => prev.map((w) => w.id === id ? { ...w, ...patch } : w));
1708
1794
  }, []);
1795
+ const duplicateWidget = useCallback((id) => {
1796
+ setWidgets((prev) => {
1797
+ const source = prev.find((w) => w.id === id);
1798
+ if (!source) return prev;
1799
+ const copy = {
1800
+ ...source,
1801
+ id: nextWidgetId(),
1802
+ title: `${source.title} (2)`,
1803
+ metrics: [...source.metrics],
1804
+ groupBy: [...source.groupBy]
1805
+ };
1806
+ const idx = prev.findIndex((w) => w.id === id);
1807
+ return [...prev.slice(0, idx + 1), copy, ...prev.slice(idx + 1)];
1808
+ });
1809
+ }, []);
1709
1810
  const addFilter = useCallback((dim) => {
1710
1811
  if (sharedFilters.some((f) => f.dimension.name === dim.name)) return;
1711
- setSharedFilters((prev) => [...prev, { dimension: dim, label: dim.displayName }]);
1812
+ setSharedFilters((prev) => [...prev, {
1813
+ dimension: dim,
1814
+ label: dim.displayName,
1815
+ timePreset: dim.type === "time_dimension" ? "12m" : null
1816
+ }]);
1712
1817
  }, [sharedFilters]);
1713
1818
  const removeFilter = useCallback((name) => {
1714
1819
  setSharedFilters((prev) => prev.filter((f) => f.dimension.name !== name));
1715
1820
  }, []);
1821
+ const updateFilterPreset = useCallback((name, preset) => {
1822
+ setSharedFilters((prev) => prev.map(
1823
+ (f) => f.dimension.name === name ? { ...f, timePreset: preset } : f
1824
+ ));
1825
+ }, []);
1716
1826
  const addFieldToWidget = useCallback((widgetId, field) => {
1717
1827
  setWidgets((prev) => prev.map((w) => {
1718
1828
  if (w.id !== widgetId) return w;
@@ -1746,11 +1856,17 @@ function BuildTab() {
1746
1856
  setCopied(true);
1747
1857
  setTimeout(() => setCopied(false), 2e3);
1748
1858
  }, [prompt]);
1749
- const handleLovable = useCallback(() => {
1750
- openInLovable(prompt);
1751
- setLovableSent(true);
1752
- setTimeout(() => setLovableSent(false), 3e3);
1753
- }, [prompt]);
1859
+ const handleLovable = useCallback(async () => {
1860
+ if (inIframe) {
1861
+ await copyToClipboard(prompt);
1862
+ setLovableSent(true);
1863
+ setTimeout(() => setLovableSent(false), 3e3);
1864
+ } else {
1865
+ openInLovable(prompt);
1866
+ setLovableSent(true);
1867
+ setTimeout(() => setLovableSent(false), 3e3);
1868
+ }
1869
+ }, [prompt, inIframe]);
1754
1870
  const filteredMetrics = useMemo(() => {
1755
1871
  const q = catalogSearch.toLowerCase();
1756
1872
  if (!q) return allMetrics;
@@ -1794,7 +1910,7 @@ function BuildTab() {
1794
1910
  )) })
1795
1911
  ] }),
1796
1912
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", flex: 1, minHeight: 0 }, children: [
1797
- /* @__PURE__ */ jsxs("div", { style: { width: 220, borderRight: "1px solid #e5e7eb", overflow: "auto", flexShrink: 0 }, children: [
1913
+ /* @__PURE__ */ jsxs("div", { style: { width: catalogWidth, borderRight: "1px solid #e5e7eb", overflow: "auto", flexShrink: 0 }, children: [
1798
1914
  /* @__PURE__ */ jsx("div", { style: { padding: "8px 12px" }, children: /* @__PURE__ */ jsx(
1799
1915
  "input",
1800
1916
  {
@@ -1820,11 +1936,40 @@ function BuildTab() {
1820
1936
  filteredDimensions.length,
1821
1937
  ")"
1822
1938
  ] }),
1823
- filteredDimensions.map((f) => /* @__PURE__ */ jsx(CatalogChip, { field: f, widgets, onAddToWidget: addFieldToWidget, onAddAsFilter: addFilter }, f.name)),
1939
+ filteredDimensions.map((f) => /* @__PURE__ */ jsx(
1940
+ CatalogChip,
1941
+ {
1942
+ field: f,
1943
+ widgets,
1944
+ onAddToWidget: addFieldToWidget,
1945
+ onAddAsFilter: addFilter,
1946
+ suffix: f.name === "metric_time" ? " (recommended)" : void 0
1947
+ },
1948
+ f.name
1949
+ )),
1824
1950
  filteredDimensions.length === 0 && /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#9ca3af", padding: "4px 0" }, children: "None found" })
1825
1951
  ] })
1826
1952
  ] })
1827
1953
  ] }),
1954
+ /* @__PURE__ */ jsx(
1955
+ "div",
1956
+ {
1957
+ onMouseDown: handleCatalogDragStart,
1958
+ style: {
1959
+ width: 4,
1960
+ cursor: "col-resize",
1961
+ backgroundColor: "transparent",
1962
+ flexShrink: 0,
1963
+ position: "relative"
1964
+ },
1965
+ onMouseEnter: (e) => {
1966
+ e.currentTarget.style.backgroundColor = "#d1d5db";
1967
+ },
1968
+ onMouseLeave: (e) => {
1969
+ e.currentTarget.style.backgroundColor = "transparent";
1970
+ }
1971
+ }
1972
+ ),
1828
1973
  /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto", padding: "12px 16px" }, children: widgets.length === 0 ? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "40px 20px", color: "#9ca3af" }, children: [
1829
1974
  /* @__PURE__ */ jsx("div", { style: { fontSize: 14, fontWeight: 500, marginBottom: 12 }, children: "Compose your dashboard" }),
1830
1975
  /* @__PURE__ */ jsx("div", { style: { fontSize: 12, marginBottom: 20 }, children: "Add widgets below, then pick metrics and dimensions from the catalog on the left." }),
@@ -1836,6 +1981,7 @@ function BuildTab() {
1836
1981
  widget: w,
1837
1982
  onUpdate: updateWidget,
1838
1983
  onRemove: removeWidget,
1984
+ onDuplicate: duplicateWidget,
1839
1985
  onRemoveField: removeFieldFromWidget,
1840
1986
  allDimensions
1841
1987
  },
@@ -1845,9 +1991,31 @@ function BuildTab() {
1845
1991
  /* @__PURE__ */ jsxs("div", { style: { marginTop: 16, padding: "12px 0", borderTop: "1px solid #f3f4f6" }, children: [
1846
1992
  /* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Shared Filters" }),
1847
1993
  /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: "Dimensions that filter all widgets together. Add from the catalog sidebar." }),
1848
- sharedFilters.length === 0 ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#d1d5db", fontStyle: "italic" }, children: "None added yet" }) : /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: sharedFilters.map((f) => /* @__PURE__ */ jsxs("span", { style: chipStyle, children: [
1849
- f.label,
1850
- /* @__PURE__ */ jsx("button", { onClick: () => removeFilter(f.dimension.name), style: chipRemoveStyle, children: "\xD7" })
1994
+ sharedFilters.length === 0 ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#d1d5db", fontStyle: "italic" }, children: "None added yet" }) : /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: sharedFilters.map((f) => /* @__PURE__ */ jsxs("div", { children: [
1995
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: /* @__PURE__ */ jsxs("span", { style: chipStyle, children: [
1996
+ f.label,
1997
+ /* @__PURE__ */ jsx("button", { onClick: () => removeFilter(f.dimension.name), style: chipRemoveStyle, children: "\xD7" })
1998
+ ] }) }),
1999
+ f.dimension.type === "time_dimension" && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4, marginTop: 4, marginLeft: 4 }, children: Object.keys(TIME_PRESET_LABELS).map((p) => /* @__PURE__ */ jsx(
2000
+ "button",
2001
+ {
2002
+ onClick: () => updateFilterPreset(f.dimension.name, p),
2003
+ style: {
2004
+ padding: "2px 8px",
2005
+ fontSize: 10,
2006
+ fontWeight: 600,
2007
+ fontFamily: "system-ui, -apple-system, sans-serif",
2008
+ borderRadius: 999,
2009
+ border: f.timePreset === p ? "1px solid #3b82f6" : "1px solid #e5e7eb",
2010
+ backgroundColor: f.timePreset === p ? "#eff6ff" : "#fff",
2011
+ color: f.timePreset === p ? "#3b82f6" : "#9ca3af",
2012
+ cursor: "pointer",
2013
+ transition: "all 0.1s"
2014
+ },
2015
+ children: TIME_PRESET_LABELS[p]
2016
+ },
2017
+ p
2018
+ )) })
1851
2019
  ] }, f.dimension.name)) })
1852
2020
  ] }),
1853
2021
  canGenerate && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
@@ -1899,8 +2067,8 @@ function BuildTab() {
1899
2067
  gap: 6
1900
2068
  },
1901
2069
  children: [
1902
- lovableSent ? "Opening Lovable..." : "Build in Lovable",
1903
- !lovableSent && /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2070
+ lovableSent ? inIframe ? "Copied! Paste into Lovable chat" : "Opening Lovable..." : inIframe ? "Send to Lovable \u2764\uFE0F" : "Build in Lovable \u2764\uFE0F",
2071
+ !lovableSent && !inIframe && /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1904
2072
  /* @__PURE__ */ jsx("path", { d: "M7 17L17 7" }),
1905
2073
  /* @__PURE__ */ jsx("path", { d: "M7 7h10v10" })
1906
2074
  ] })
@@ -1914,7 +2082,8 @@ function CatalogChip({
1914
2082
  field,
1915
2083
  widgets,
1916
2084
  onAddToWidget,
1917
- onAddAsFilter
2085
+ onAddAsFilter,
2086
+ suffix
1918
2087
  }) {
1919
2088
  const [menuOpen, setMenuOpen] = useState(false);
1920
2089
  const typeIcon = field.type === "metric" ? "\u2581" : field.type === "time_dimension" ? "\u25F7" : "\u2022";
@@ -1956,7 +2125,10 @@ ${field.description}`,
1956
2125
  },
1957
2126
  children: [
1958
2127
  /* @__PURE__ */ jsx("span", { style: { color: typeColor, fontSize: 10, flexShrink: 0 }, children: typeIcon }),
1959
- /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: field.displayName })
2128
+ /* @__PURE__ */ jsxs("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
2129
+ field.displayName,
2130
+ suffix && /* @__PURE__ */ jsx("span", { style: { color: "#9ca3af", fontWeight: 400 }, children: suffix })
2131
+ ] })
1960
2132
  ]
1961
2133
  }
1962
2134
  ),
@@ -2046,6 +2218,7 @@ function WidgetCard({
2046
2218
  widget,
2047
2219
  onUpdate,
2048
2220
  onRemove,
2221
+ onDuplicate,
2049
2222
  onRemoveField,
2050
2223
  allDimensions
2051
2224
  }) {
@@ -2084,6 +2257,7 @@ function WidgetCard({
2084
2257
  },
2085
2258
  wt.type
2086
2259
  )) }),
2260
+ /* @__PURE__ */ jsx("button", { onClick: () => onDuplicate(widget.id), style: { background: "none", border: "none", cursor: "pointer", fontSize: 12, color: "#d1d5db", padding: "0 2px" }, title: "Duplicate widget", children: "\u2398" }),
2087
2261
  /* @__PURE__ */ jsx("button", { onClick: () => onRemove(widget.id), style: { background: "none", border: "none", cursor: "pointer", fontSize: 14, color: "#d1d5db", padding: "0 2px" }, title: "Remove widget", children: "\xD7" })
2088
2262
  ] }),
2089
2263
  !collapsed && /* @__PURE__ */ jsxs("div", { style: { padding: "8px 12px" }, children: [
@@ -2142,12 +2316,9 @@ var backdropStyle = {
2142
2316
  zIndex: 1e5
2143
2317
  };
2144
2318
  var modalStyle = {
2145
- position: "fixed",
2146
- top: "50%",
2147
- left: "50%",
2148
- transform: "translate(-50%, -50%)",
2149
2319
  zIndex: 100001,
2150
2320
  width: "min(900px, 95vw)",
2321
+ height: "85vh",
2151
2322
  maxHeight: "85vh",
2152
2323
  backgroundColor: "#fff",
2153
2324
  borderRadius: 12,
@@ -2291,6 +2462,6 @@ var chipRemoveStyle = {
2291
2462
  lineHeight: 1
2292
2463
  };
2293
2464
 
2294
- export { AdminDashboard, DataCatalog, DataInspector, Inspectable, MetricPicker, ResultsTable, analyzeChartData, canOpenInLovable, generateMigrationPrompt, generateReportPrompt, matchField, matchFields, openInLovable };
2465
+ export { AdminDashboard, DataCatalog, DataInspector, Inspectable, MetricPicker, ResultsTable, analyzeChartData, canOpenInLovable, generateMigrationPrompt, generateReportPrompt, isRunningInIframe, matchField, matchFields, openInLovable };
2295
2466
  //# sourceMappingURL=components.js.map
2296
2467
  //# sourceMappingURL=components.js.map