@shapesos/clay 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +60 -0
  2. package/dist/artifacts.cjs +1420 -245
  3. package/dist/artifacts.cjs.map +1 -1
  4. package/dist/artifacts.d.cts +167 -6
  5. package/dist/artifacts.d.ts +167 -6
  6. package/dist/artifacts.js +18 -3
  7. package/dist/blocks.cjs +1696 -532
  8. package/dist/blocks.cjs.map +1 -1
  9. package/dist/blocks.css +1 -1
  10. package/dist/blocks.d.cts +21 -10
  11. package/dist/blocks.d.ts +21 -10
  12. package/dist/blocks.js +7 -3
  13. package/dist/capture.cjs +220 -0
  14. package/dist/capture.cjs.map +1 -0
  15. package/dist/capture.d.cts +113 -0
  16. package/dist/capture.d.ts +113 -0
  17. package/dist/capture.js +17 -0
  18. package/dist/chart.cjs +594 -0
  19. package/dist/chart.cjs.map +1 -0
  20. package/dist/chart.d.cts +439 -0
  21. package/dist/chart.d.ts +439 -0
  22. package/dist/chart.js +32 -0
  23. package/dist/chart.js.map +1 -0
  24. package/dist/chat.cjs +1687 -532
  25. package/dist/chat.cjs.map +1 -1
  26. package/dist/chat.d.cts +3 -2
  27. package/dist/chat.d.ts +3 -2
  28. package/dist/chat.js +8 -4
  29. package/dist/{chunk-MXOPG747.js → chunk-26XVDY6Z.js} +8 -8
  30. package/dist/chunk-26XVDY6Z.js.map +1 -0
  31. package/dist/{chunk-L35M3OD5.js → chunk-4MZZH3WX.js} +5 -11
  32. package/dist/chunk-4MZZH3WX.js.map +1 -0
  33. package/dist/chunk-DT44KNCV.js +1004 -0
  34. package/dist/chunk-DT44KNCV.js.map +1 -0
  35. package/dist/chunk-JAGNXEWK.js +116 -0
  36. package/dist/chunk-JAGNXEWK.js.map +1 -0
  37. package/dist/chunk-LXYCT4YO.js +7 -0
  38. package/dist/chunk-LXYCT4YO.js.map +1 -0
  39. package/dist/chunk-P6GUNIAE.js +11 -0
  40. package/dist/chunk-P6GUNIAE.js.map +1 -0
  41. package/dist/{chunk-OUW6PUEB.js → chunk-QH2WW2MW.js} +7 -7
  42. package/dist/chunk-QXGYMDIA.js +477 -0
  43. package/dist/chunk-QXGYMDIA.js.map +1 -0
  44. package/dist/chunk-RSRNSWJN.js +1 -0
  45. package/dist/chunk-RSRNSWJN.js.map +1 -0
  46. package/dist/dom-to-image-NATF3446.js +21 -0
  47. package/dist/dom-to-image-NATF3446.js.map +1 -0
  48. package/dist/index.cjs +1489 -317
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +4 -3
  51. package/dist/index.d.ts +4 -3
  52. package/dist/index.js +28 -15
  53. package/dist/table.js +2 -1
  54. package/dist/types-B2aYk82c.d.cts +29 -0
  55. package/dist/types-B2aYk82c.d.ts +29 -0
  56. package/dist/types-BzXPBy6s.d.cts +330 -0
  57. package/dist/{types-DuuRI4ll.d.cts → types-CW45GBXl.d.cts} +31 -4
  58. package/dist/{types-C9XX-Uhk.d.ts → types-D1b8v7EY.d.ts} +31 -4
  59. package/dist/types-GCkXbyFm.d.ts +330 -0
  60. package/package.json +24 -2
  61. package/dist/chunk-BX5TCEPR.js +0 -436
  62. package/dist/chunk-BX5TCEPR.js.map +0 -1
  63. package/dist/chunk-L35M3OD5.js.map +0 -1
  64. package/dist/chunk-MEJESPTZ.js +0 -1
  65. package/dist/chunk-MXOPG747.js.map +0 -1
  66. package/dist/types-3Gzk7cRt.d.cts +0 -121
  67. package/dist/types-3Gzk7cRt.d.ts +0 -121
  68. /package/dist/{chunk-MEJESPTZ.js.map → capture.js.map} +0 -0
  69. /package/dist/{chunk-OUW6PUEB.js.map → chunk-QH2WW2MW.js.map} +0 -0
@@ -0,0 +1,1004 @@
1
+ import {
2
+ CAPTURE_EXCLUDE_ATTRIBUTE
3
+ } from "./chunk-LXYCT4YO.js";
4
+ import {
5
+ BarChart,
6
+ LineChart,
7
+ PieChart
8
+ } from "./chunk-QXGYMDIA.js";
9
+ import {
10
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableHead,
14
+ TableHeader,
15
+ TableRow,
16
+ useScrollShadow
17
+ } from "./chunk-4MZZH3WX.js";
18
+ import {
19
+ cn
20
+ } from "./chunk-P6GUNIAE.js";
21
+ import {
22
+ IconButton
23
+ } from "./chunk-OBOXCBDL.js";
24
+
25
+ // src/components/artifacts/artifact-base.ts
26
+ var artifactTypes = {
27
+ TABLE: "TABLE",
28
+ CHART: "CHART"
29
+ };
30
+
31
+ // src/components/artifacts/chart-artifact/types.ts
32
+ var DEFAULT_CHART_ARTIFACT_LABELS = {
33
+ download: "Download",
34
+ downloadCsv: "Download CSV",
35
+ downloadPng: "Download PNG",
36
+ copyAsImage: "Copy Image",
37
+ unavailable: "Chart data is unavailable.",
38
+ loadError: "Couldn't load chart data.",
39
+ loading: "Loading chart\u2026",
40
+ empty: "No data to chart.",
41
+ othersCategoryLabels: ["Others", "Other"]
42
+ };
43
+
44
+ // src/components/artifacts/table-artifact/types.ts
45
+ var DEFAULT_TABLE_ARTIFACT_LABELS = {
46
+ download: "Download CSV",
47
+ unavailable: "Table data is unavailable.",
48
+ loadError: "Couldn't load table data.",
49
+ loading: "Loading table\u2026",
50
+ empty: "No rows to display."
51
+ };
52
+
53
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
54
+ import { forwardRef, createElement } from "react";
55
+
56
+ // node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
57
+ var defaultAttributes = {
58
+ outline: {
59
+ xmlns: "http://www.w3.org/2000/svg",
60
+ width: 24,
61
+ height: 24,
62
+ viewBox: "0 0 24 24",
63
+ fill: "none",
64
+ stroke: "currentColor",
65
+ strokeWidth: 2,
66
+ strokeLinecap: "round",
67
+ strokeLinejoin: "round"
68
+ },
69
+ filled: {
70
+ xmlns: "http://www.w3.org/2000/svg",
71
+ width: 24,
72
+ height: 24,
73
+ viewBox: "0 0 24 24",
74
+ fill: "currentColor",
75
+ stroke: "none"
76
+ }
77
+ };
78
+
79
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
80
+ var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
81
+ const Component = forwardRef(
82
+ ({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => createElement(
83
+ "svg",
84
+ {
85
+ ref,
86
+ ...defaultAttributes[type],
87
+ width: size,
88
+ height: size,
89
+ className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
90
+ ...type === "filled" ? {
91
+ fill: color
92
+ } : {
93
+ strokeWidth: stroke,
94
+ stroke: color
95
+ },
96
+ ...rest
97
+ },
98
+ [
99
+ title && createElement("title", { key: "svg-title" }, title),
100
+ ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
101
+ ...Array.isArray(children) ? children : [children]
102
+ ]
103
+ )
104
+ );
105
+ Component.displayName = `${iconNamePascal}`;
106
+ return Component;
107
+ };
108
+
109
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs
110
+ var __iconNode = [["path", { "d": "M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0", "key": "svg-0" }], ["path", { "d": "M12 8v4", "key": "svg-1" }], ["path", { "d": "M12 16h.01", "key": "svg-2" }]];
111
+ var IconAlertCircle = createReactComponent("outline", "alert-circle", "AlertCircle", __iconNode);
112
+
113
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
114
+ var __iconNode2 = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
115
+ var IconCheck = createReactComponent("outline", "check", "Check", __iconNode2);
116
+
117
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
118
+ var __iconNode3 = [["path", { "d": "M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666", "key": "svg-0" }], ["path", { "d": "M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1", "key": "svg-1" }]];
119
+ var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode3);
120
+
121
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconDownload.mjs
122
+ var __iconNode4 = [["path", { "d": "M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2", "key": "svg-0" }], ["path", { "d": "M7 11l5 5l5 -5", "key": "svg-1" }], ["path", { "d": "M12 4l0 12", "key": "svg-2" }]];
123
+ var IconDownload = createReactComponent("outline", "download", "Download", __iconNode4);
124
+
125
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconFileSpreadsheet.mjs
126
+ var __iconNode5 = [["path", { "d": "M14 3v4a1 1 0 0 0 1 1h4", "key": "svg-0" }], ["path", { "d": "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2", "key": "svg-1" }], ["path", { "d": "M8 11h8v7h-8l0 -7", "key": "svg-2" }], ["path", { "d": "M8 15h8", "key": "svg-3" }], ["path", { "d": "M11 11v7", "key": "svg-4" }]];
127
+ var IconFileSpreadsheet = createReactComponent("outline", "file-spreadsheet", "FileSpreadsheet", __iconNode5);
128
+
129
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconLoader2.mjs
130
+ var __iconNode6 = [["path", { "d": "M12 3a9 9 0 1 0 9 9", "key": "svg-0" }]];
131
+ var IconLoader2 = createReactComponent("outline", "loader-2", "Loader2", __iconNode6);
132
+
133
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconPhoto.mjs
134
+ var __iconNode7 = [["path", { "d": "M15 8h.01", "key": "svg-0" }], ["path", { "d": "M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12", "key": "svg-1" }], ["path", { "d": "M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5", "key": "svg-2" }], ["path", { "d": "M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3", "key": "svg-3" }]];
135
+ var IconPhoto = createReactComponent("outline", "photo", "Photo", __iconNode7);
136
+
137
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs
138
+ var __iconNode8 = [["path", { "d": "M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3", "key": "svg-0" }]];
139
+ var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", __iconNode8);
140
+
141
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs
142
+ var __iconNode9 = [["path", { "d": "M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3", "key": "svg-0" }]];
143
+ var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode9);
144
+
145
+ // src/components/artifacts/artifact-copy-as-image-button/artifact-copy-as-image-button.tsx
146
+ import { useCallback as useCallback2 } from "react";
147
+
148
+ // src/components/ui/tooltip.tsx
149
+ import * as React from "react";
150
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
151
+ import { jsx } from "react/jsx-runtime";
152
+ var TooltipProvider = TooltipPrimitive.Provider;
153
+ var Tooltip = TooltipPrimitive.Root;
154
+ var TooltipTrigger = TooltipPrimitive.Trigger;
155
+ var TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(
156
+ TooltipPrimitive.Content,
157
+ {
158
+ ref,
159
+ sideOffset,
160
+ className: cn(
161
+ "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
162
+ className
163
+ ),
164
+ ...props
165
+ }
166
+ ));
167
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
168
+
169
+ // src/components/artifacts/artifact-copy-as-image-button/use-copy-as-image.ts
170
+ import { useCallback, useEffect, useRef, useState } from "react";
171
+ var DEFAULT_RESET_AFTER_MS = 1500;
172
+ function useCopyAsImage(targetRef, options = {}) {
173
+ const [status, setStatus] = useState("idle");
174
+ const [error, setError] = useState(null);
175
+ const resetTimerRef = useRef(null);
176
+ useEffect(() => {
177
+ return () => {
178
+ if (resetTimerRef.current !== null) clearTimeout(resetTimerRef.current);
179
+ };
180
+ }, []);
181
+ const copy = useCallback(async () => {
182
+ if (resetTimerRef.current !== null) {
183
+ clearTimeout(resetTimerRef.current);
184
+ resetTimerRef.current = null;
185
+ }
186
+ const { resetAfterMs = DEFAULT_RESET_AFTER_MS, ...nodeOptions } = options;
187
+ const scheduleReset = () => {
188
+ if (resetAfterMs <= 0) return;
189
+ resetTimerRef.current = setTimeout(() => {
190
+ resetTimerRef.current = null;
191
+ setStatus("idle");
192
+ setError(null);
193
+ }, resetAfterMs);
194
+ };
195
+ const node = targetRef.current;
196
+ if (!node) {
197
+ const err = new Error("ArtifactCopyAsImageButton: target ref is not attached to a DOM node.");
198
+ setError(err);
199
+ setStatus("error");
200
+ scheduleReset();
201
+ throw err;
202
+ }
203
+ setError(null);
204
+ setStatus("working");
205
+ try {
206
+ const { copyNodeAsPng } = await import("./dom-to-image-NATF3446.js");
207
+ await copyNodeAsPng(node, nodeOptions);
208
+ setStatus("success");
209
+ scheduleReset();
210
+ } catch (err) {
211
+ const wrapped = err instanceof Error ? err : new Error(String(err));
212
+ setError(wrapped);
213
+ setStatus("error");
214
+ scheduleReset();
215
+ throw wrapped;
216
+ }
217
+ }, [targetRef, options]);
218
+ return { status, error, copy };
219
+ }
220
+
221
+ // src/components/artifacts/artifact-copy-as-image-button/artifact-copy-as-image-button.tsx
222
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
223
+ var MAX_ERROR_MESSAGE_CHARS = 120;
224
+ function truncateErrorMessage(message, max = MAX_ERROR_MESSAGE_CHARS) {
225
+ return message.length <= max ? message : `${message.slice(0, max - 1)}\u2026`;
226
+ }
227
+ var DEFAULT_LABEL = "Copy Image";
228
+ var ICON_BY_STATUS = {
229
+ // `IconCopy` reads as the clipboard-copy affordance (action-shaped), distinct from the
230
+ // download menu's format-shaped icons. The button only ever puts bytes on the clipboard, so
231
+ // the visual signal stays consistent across copy-aware artifact types.
232
+ idle: IconCopy,
233
+ working: IconLoader2,
234
+ success: IconCheck,
235
+ error: IconAlertCircle
236
+ };
237
+ var TRANSIENT_TOOLTIP_BY_STATUS = {
238
+ working: "Copying\u2026",
239
+ success: "Copied",
240
+ error: "Copy failed"
241
+ };
242
+ function ArtifactCopyAsImageButton({
243
+ targetRef,
244
+ label = DEFAULT_LABEL,
245
+ disabled = false,
246
+ onClick
247
+ }) {
248
+ const { status, error, copy } = useCopyAsImage(targetRef);
249
+ const handleClick = useCallback2(() => {
250
+ onClick?.();
251
+ copy().catch((err) => {
252
+ console.warn("[clay] ArtifactCopyAsImageButton: copy failed", err);
253
+ });
254
+ }, [copy, onClick]);
255
+ const statusLabel = status === "idle" ? label : status === "error" && error ? `Copy failed: ${truncateErrorMessage(error.message)}` : TRANSIENT_TOOLTIP_BY_STATUS[status] ?? label;
256
+ return /* @__PURE__ */ jsx2(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
257
+ /* @__PURE__ */ jsx2(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx2(
258
+ IconButton,
259
+ {
260
+ icon: ICON_BY_STATUS[status],
261
+ size: "small",
262
+ onClick: handleClick,
263
+ disabled: disabled || status === "working",
264
+ "aria-label": statusLabel
265
+ }
266
+ ) }),
267
+ /* @__PURE__ */ jsx2(TooltipContent, { children: statusLabel })
268
+ ] }) });
269
+ }
270
+
271
+ // src/components/artifacts/artifact-download-button/artifact-download-button.tsx
272
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
273
+ var DEFAULT_LABEL2 = "Download CSV";
274
+ function ArtifactDownloadButton({
275
+ href,
276
+ label = DEFAULT_LABEL2,
277
+ onClick
278
+ }) {
279
+ if (!isSafeDownloadUrl(href)) return null;
280
+ return /* @__PURE__ */ jsx3(TooltipProvider, { children: /* @__PURE__ */ jsxs2(Tooltip, { children: [
281
+ /* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(
282
+ IconButton,
283
+ {
284
+ as: "a",
285
+ href,
286
+ download: true,
287
+ rel: "noopener noreferrer",
288
+ onClick,
289
+ icon: IconDownload,
290
+ size: "small",
291
+ "aria-label": label
292
+ }
293
+ ) }),
294
+ /* @__PURE__ */ jsx3(TooltipContent, { children: label })
295
+ ] }) });
296
+ }
297
+ function isSafeDownloadUrl(url) {
298
+ try {
299
+ const parsed = new URL(url);
300
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
301
+ } catch {
302
+ return false;
303
+ }
304
+ }
305
+
306
+ // src/components/artifacts/artifact-download-menu/artifact-download-menu.tsx
307
+ import { useCallback as useCallback3 } from "react";
308
+
309
+ // src/components/ui/dropdown-menu.tsx
310
+ import * as React2 from "react";
311
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
312
+ import { jsx as jsx4 } from "react/jsx-runtime";
313
+ var DropdownMenu = DropdownMenuPrimitive.Root;
314
+ var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
315
+ var DropdownMenuContent = React2.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx4(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx4(
316
+ DropdownMenuPrimitive.Content,
317
+ {
318
+ ref,
319
+ sideOffset,
320
+ className: cn(
321
+ // Stacking is consumer-configurable via the `--z-popover` CSS variable (default `99999`,
322
+ // declared in clay's generated `_theme-tokens.css` :root block). Consumers whose chat or
323
+ // modal layer sits even higher override on their own root once:
324
+ // :root { --z-popover: 9999999; }
325
+ // and every clay floating surface picks the new value up via cascade. The fallback in
326
+ // `var(...,99999)` only kicks in if the consumer also strips the clay stylesheet — which
327
+ // they shouldn't, but the safety net costs nothing.
328
+ "z-[var(--z-popover,99999)] min-w-[10rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
329
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
330
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
331
+ className
332
+ ),
333
+ ...props
334
+ }
335
+ ) }));
336
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
337
+ var DropdownMenuItem = React2.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx4(
338
+ DropdownMenuPrimitive.Item,
339
+ {
340
+ ref,
341
+ className: cn(
342
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors",
343
+ "focus:bg-accent focus:text-accent-foreground",
344
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
345
+ "[&_svg]:size-4 [&_svg]:shrink-0",
346
+ inset && "pl-8",
347
+ className
348
+ ),
349
+ ...props
350
+ }
351
+ ));
352
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
353
+ var DropdownMenuSeparator = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(DropdownMenuPrimitive.Separator, { ref, className: cn("-mx-1 my-1 h-px bg-muted", className), ...props }));
354
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
355
+ var DropdownMenuLabel = React2.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx4(
356
+ DropdownMenuPrimitive.Label,
357
+ {
358
+ ref,
359
+ className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className),
360
+ ...props
361
+ }
362
+ ));
363
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
364
+
365
+ // src/components/artifacts/artifact-download-menu/artifact-download-menu.tsx
366
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
367
+ var DEFAULT_TRIGGER_LABEL = "Download";
368
+ var DEFAULT_CSV_LABEL = "Download CSV";
369
+ var DEFAULT_PNG_LABEL = "Download PNG";
370
+ var DEFAULT_PNG_BASENAME = "artifact";
371
+ function ArtifactDownloadMenu({
372
+ actions,
373
+ triggerLabel = DEFAULT_TRIGGER_LABEL
374
+ }) {
375
+ const { csv, png } = actions;
376
+ const showCsv = csv !== void 0 && isSafeDownloadUrl(csv.href);
377
+ const showPng = png !== void 0;
378
+ const handlePngSelect = useCallback3(async () => {
379
+ if (!png) return;
380
+ png.onSelect?.();
381
+ const node = png.targetRef.current;
382
+ if (!node) return;
383
+ const filename = `${png.filename ?? DEFAULT_PNG_BASENAME}.png`;
384
+ try {
385
+ const { downloadNodeAsPng } = await import("./dom-to-image-NATF3446.js");
386
+ await downloadNodeAsPng(node, filename);
387
+ } catch (err) {
388
+ console.warn("[clay] ArtifactDownloadMenu: PNG download failed", err);
389
+ }
390
+ }, [png]);
391
+ return /* @__PURE__ */ jsxs3(DropdownMenu, { children: [
392
+ /* @__PURE__ */ jsx5(TooltipProvider, { children: /* @__PURE__ */ jsxs3(Tooltip, { children: [
393
+ /* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(IconButton, { icon: IconDownload, size: "small", "aria-label": triggerLabel }) }) }),
394
+ /* @__PURE__ */ jsx5(TooltipContent, { children: triggerLabel })
395
+ ] }) }),
396
+ /* @__PURE__ */ jsxs3(DropdownMenuContent, { align: "end", children: [
397
+ showCsv ? (
398
+ // `asChild` lets Radix render the item as our anchor — the browser handles the
399
+ // download natively and respects `Content-Disposition`. The analytics callback rides
400
+ // on the anchor's own `onClick` (rather than Radix's `onSelect`) so the same handler
401
+ // fires whether the user mouse-clicks the item, hits Enter on it, or activates it via
402
+ // touch — `onSelect` is reliable only for keyboard activation when `asChild` is in use.
403
+ /* @__PURE__ */ jsx5(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsxs3("a", { href: csv.href, download: true, rel: "noopener noreferrer", onClick: csv.onSelect, children: [
404
+ /* @__PURE__ */ jsx5(IconFileSpreadsheet, {}),
405
+ csv.label ?? DEFAULT_CSV_LABEL
406
+ ] }) })
407
+ ) : null,
408
+ showPng ? /* @__PURE__ */ jsxs3(DropdownMenuItem, { onSelect: handlePngSelect, disabled: png.disabled ?? false, children: [
409
+ /* @__PURE__ */ jsx5(IconPhoto, {}),
410
+ png.label ?? DEFAULT_PNG_LABEL
411
+ ] }) : null
412
+ ] })
413
+ ] });
414
+ }
415
+
416
+ // src/components/artifacts/chart-artifact/chart-artifact.tsx
417
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from "react";
418
+
419
+ // src/components/ai-elements/artifact.tsx
420
+ import { forwardRef as forwardRef4 } from "react";
421
+ import { jsx as jsx6 } from "react/jsx-runtime";
422
+ var Artifact = forwardRef4(({ className, ...props }, ref) => /* @__PURE__ */ jsx6(
423
+ "div",
424
+ {
425
+ ref,
426
+ className: cn("flex flex-col overflow-hidden rounded-lg border bg-background shadow-sm", className),
427
+ ...props
428
+ }
429
+ ));
430
+ Artifact.displayName = "Artifact";
431
+ var ArtifactHeader = ({ className, ...props }) => /* @__PURE__ */ jsx6("div", { className: cn("flex items-center justify-between border-b bg-muted/50 px-4 py-3", className), ...props });
432
+ var ArtifactTitle = ({ className, ...props }) => /* @__PURE__ */ jsx6("p", { className: cn("font-medium text-foreground text-sm", className), ...props });
433
+ var ArtifactActions = ({ className, ...props }) => /* @__PURE__ */ jsx6("div", { className: cn("flex items-center gap-1", className), ...props });
434
+ var ArtifactContent = ({ className, ...props }) => /* @__PURE__ */ jsx6("div", { className: cn("flex-1 overflow-auto p-4", className), ...props });
435
+
436
+ // src/components/artifacts/status-message.tsx
437
+ import { createElement as createElement2 } from "react";
438
+ function StatusMessage({ $tone, children }) {
439
+ return createElement2(
440
+ "div",
441
+ {
442
+ className: `py-6 px-2 text-center text-sm font-medium leading-5 ${$tone === "error" ? "text-red-600" : "text-brown-70"}`
443
+ },
444
+ children
445
+ );
446
+ }
447
+
448
+ // src/components/artifacts/chart-artifact/chart-variant-services/constants.ts
449
+ var VARIANT_CHART_HEIGHT = "100%";
450
+
451
+ // src/components/artifacts/chart-artifact/chart-variant-services/bar-chart-variant-component.tsx
452
+ import { jsx as jsx7 } from "react/jsx-runtime";
453
+ function BarChartVariantComponent({ config, rows }) {
454
+ return /* @__PURE__ */ jsx7(
455
+ BarChart,
456
+ {
457
+ data: rows,
458
+ xKey: config.xKey,
459
+ series: config.series,
460
+ stacked: config.stacked,
461
+ height: VARIANT_CHART_HEIGHT
462
+ }
463
+ );
464
+ }
465
+
466
+ // src/components/artifacts/chart-artifact/chart-variant-services/shared-column-keys.ts
467
+ function seriesBasedColumnKeys(config) {
468
+ return [config.xKey, ...config.series.map((s) => s.key)];
469
+ }
470
+ function pieColumnKeys(config) {
471
+ return [config.categoryKey, config.valueKey];
472
+ }
473
+
474
+ // src/components/artifacts/chart-artifact/chart-variant-services/shared-validators.ts
475
+ function isValidChartSeries(series) {
476
+ if (!series || typeof series !== "object") return false;
477
+ const candidate = series;
478
+ if (typeof candidate.key !== "string" || candidate.key.length === 0) return false;
479
+ if (typeof candidate.label !== "string") return false;
480
+ return true;
481
+ }
482
+
483
+ // src/components/artifacts/chart-artifact/chart-variant-services/bar-chart-variant-service.tsx
484
+ function isValidBarChartConfig(config) {
485
+ if (!config || typeof config !== "object") return false;
486
+ const candidate = config;
487
+ if (candidate.variant !== "BAR") return false;
488
+ if (typeof candidate.xKey !== "string" || candidate.xKey.length === 0) return false;
489
+ if (!Array.isArray(candidate.series) || candidate.series.length === 0) return false;
490
+ if (!candidate.series.every(isValidChartSeries)) return false;
491
+ return true;
492
+ }
493
+ var BarChartVariantService = {
494
+ variant: "BAR",
495
+ isValidConfig: isValidBarChartConfig,
496
+ columnKeys: seriesBasedColumnKeys,
497
+ Component: BarChartVariantComponent
498
+ };
499
+
500
+ // src/components/artifacts/chart-artifact/chart-variant-services/line-chart-variant-component.tsx
501
+ import { jsx as jsx8 } from "react/jsx-runtime";
502
+ function LineChartVariantComponent({ config, rows }) {
503
+ return /* @__PURE__ */ jsx8(LineChart, { data: rows, xKey: config.xKey, series: config.series, height: VARIANT_CHART_HEIGHT });
504
+ }
505
+
506
+ // src/components/artifacts/chart-artifact/chart-variant-services/line-chart-variant-service.tsx
507
+ function isValidLineChartConfig(config) {
508
+ if (!config || typeof config !== "object") return false;
509
+ const candidate = config;
510
+ if (candidate.variant !== "LINE") return false;
511
+ if (typeof candidate.xKey !== "string" || candidate.xKey.length === 0) return false;
512
+ if (!Array.isArray(candidate.series) || candidate.series.length === 0) return false;
513
+ if (!candidate.series.every(isValidChartSeries)) return false;
514
+ return true;
515
+ }
516
+ var LineChartVariantService = {
517
+ variant: "LINE",
518
+ isValidConfig: isValidLineChartConfig,
519
+ columnKeys: seriesBasedColumnKeys,
520
+ Component: LineChartVariantComponent
521
+ };
522
+
523
+ // src/components/artifacts/chart-artifact/chart-variant-services/pie-chart-variant-component.tsx
524
+ import { jsx as jsx9 } from "react/jsx-runtime";
525
+ function PieChartVariantComponent({
526
+ config,
527
+ rows,
528
+ labels
529
+ }) {
530
+ return /* @__PURE__ */ jsx9(
531
+ PieChart,
532
+ {
533
+ data: rows,
534
+ categoryKey: config.categoryKey,
535
+ valueKey: config.valueKey,
536
+ othersCategoryLabels: labels.othersCategoryLabels,
537
+ height: VARIANT_CHART_HEIGHT
538
+ }
539
+ );
540
+ }
541
+
542
+ // src/components/artifacts/chart-artifact/chart-variant-services/pie-chart-variant-service.tsx
543
+ function isValidPieChartConfig(config) {
544
+ if (!config || typeof config !== "object") return false;
545
+ const candidate = config;
546
+ if (candidate.variant !== "PIE") return false;
547
+ if (typeof candidate.categoryKey !== "string" || candidate.categoryKey.length === 0) return false;
548
+ if (typeof candidate.valueKey !== "string" || candidate.valueKey.length === 0) return false;
549
+ if (!Array.isArray(candidate.series)) return false;
550
+ return true;
551
+ }
552
+ var PieChartVariantService = {
553
+ variant: "PIE",
554
+ isValidConfig: isValidPieChartConfig,
555
+ columnKeys: pieColumnKeys,
556
+ Component: PieChartVariantComponent
557
+ };
558
+
559
+ // src/components/artifacts/chart-artifact/chart-variant-services/index.ts
560
+ var ChartVariantServices = {
561
+ LINE: LineChartVariantService,
562
+ BAR: BarChartVariantService,
563
+ PIE: PieChartVariantService
564
+ };
565
+
566
+ // src/components/artifacts/chart-artifact/chart-artifact-content.tsx
567
+ import { jsx as jsx10 } from "react/jsx-runtime";
568
+ function ChartArtifactContent({ config, rows, labels }) {
569
+ const service = ChartVariantServices[config.variant];
570
+ if (!service) {
571
+ return /* @__PURE__ */ jsx10(StatusMessage, { $tone: "info", children: labels.unavailable });
572
+ }
573
+ const Component = service.Component;
574
+ return /* @__PURE__ */ jsx10(Component, { config, rows, labels });
575
+ }
576
+
577
+ // src/components/artifacts/parse-csv.ts
578
+ import Papa from "papaparse";
579
+ function parseCsv(text, columns) {
580
+ const result = Papa.parse(text, {
581
+ header: true,
582
+ skipEmptyLines: true,
583
+ dynamicTyping: false
584
+ });
585
+ const csvHeaders = new Set(result.meta?.fields ?? []);
586
+ const presentColumns = columns.filter((col) => csvHeaders.has(col.key));
587
+ if (presentColumns.length === 0) return [];
588
+ return result.data.map((rawRow) => {
589
+ const row = {};
590
+ for (const column of presentColumns) {
591
+ const value = rawRow[column.key];
592
+ row[column.key] = value === void 0 || value === "" ? null : value;
593
+ }
594
+ return row;
595
+ });
596
+ }
597
+
598
+ // src/components/artifacts/use-rows-artifact-data.ts
599
+ import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
600
+ var LOADING = { status: "loading", rows: null, error: null };
601
+ var UNAVAILABLE = { status: "unavailable", rows: null, error: null };
602
+ var INITIAL_INTERNAL = { cacheKey: null, data: LOADING };
603
+ function columnsFingerprint(columns) {
604
+ return columns.map((c) => c.key).join("|");
605
+ }
606
+ function scrubUrl(url) {
607
+ const q = url.indexOf("?");
608
+ return q === -1 ? url : `${url.slice(0, q)}?<redacted>`;
609
+ }
610
+ function useRowsArtifactData(presignedUrl, columns, parse) {
611
+ const colsKey = useMemo(() => columnsFingerprint(columns), [columns]);
612
+ const safeUrl = presignedUrl && isSafeDownloadUrl(presignedUrl) ? presignedUrl : null;
613
+ const cacheKey = safeUrl ? `${safeUrl}::${colsKey}` : null;
614
+ const [state, setState] = useState2(INITIAL_INTERNAL);
615
+ useEffect2(() => {
616
+ if (!safeUrl) return;
617
+ const controller = new AbortController();
618
+ fetch(safeUrl, {
619
+ signal: controller.signal,
620
+ headers: { Accept: "text/csv" },
621
+ // Presigned URLs are cross-origin S3 in production and don't need credentials. Explicit
622
+ // omit so a same-origin URL (compromised payload, local dev) can't accidentally receive
623
+ // session cookies or auth headers.
624
+ credentials: "omit"
625
+ }).then(async (response) => {
626
+ if (!response.ok) throw new Error(`Failed to load artifact data (${response.status})`);
627
+ return response.text();
628
+ }).then((text) => {
629
+ if (controller.signal.aborted) return;
630
+ try {
631
+ const rows = parse(text, columns);
632
+ setState({ cacheKey: `${safeUrl}::${colsKey}`, data: { status: "ready", rows, error: null } });
633
+ } catch (parseError) {
634
+ const err = parseError instanceof Error ? parseError : new Error(String(parseError));
635
+ console.error("[ArtifactData] CSV parse failed:", err.message);
636
+ setState({ cacheKey: `${safeUrl}::${colsKey}`, data: { status: "error", rows: null, error: err } });
637
+ }
638
+ }).catch((error) => {
639
+ if (controller.signal.aborted) return;
640
+ const err = error instanceof Error ? error : new Error(String(error));
641
+ console.error(`[ArtifactData] Fetch failed [${scrubUrl(safeUrl)}]:`, err.message);
642
+ setState({ cacheKey: `${safeUrl}::${colsKey}`, data: { status: "error", rows: null, error: err } });
643
+ });
644
+ return () => {
645
+ controller.abort();
646
+ };
647
+ }, [safeUrl, colsKey]);
648
+ if (!safeUrl) return UNAVAILABLE;
649
+ if (state.cacheKey !== cacheKey) return LOADING;
650
+ return state.data;
651
+ }
652
+
653
+ // src/components/artifacts/chart-artifact/use-chart-artifact-data.ts
654
+ function useChartArtifactData(presignedUrl, config) {
655
+ const service = ChartVariantServices[config.variant];
656
+ const keys = service ? service.columnKeys(config) : [];
657
+ const columns = keys.map((key) => ({ key }));
658
+ return useRowsArtifactData(presignedUrl, columns, parseCsv);
659
+ }
660
+
661
+ // src/components/artifacts/chart-artifact/validate.ts
662
+ function isValidChartConfig(config) {
663
+ if (!config || typeof config !== "object") return false;
664
+ const variant = config.variant;
665
+ if (typeof variant !== "string") return false;
666
+ const service = ChartVariantServices[variant];
667
+ if (!service) return false;
668
+ return service.isValidConfig(config);
669
+ }
670
+
671
+ // src/components/artifacts/chart-artifact/chart-artifact.tsx
672
+ import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
673
+ var TITLE_CLASSES = "text-sm font-semibold text-foreground";
674
+ var CHART_HEIGHT = 320;
675
+ function ChartArtifact({ artifact, labels, callbacks }) {
676
+ const validConfig = isValidChartConfig(artifact.config);
677
+ const cardRef = useRef2(null);
678
+ useEffect3(() => {
679
+ if (validConfig) return;
680
+ console.warn("[clay] ChartArtifact: malformed `config` \u2014 skipping render.");
681
+ }, [validConfig]);
682
+ const presignedUrl = artifact.protectedAsset?.presignedUrl ?? null;
683
+ const resolvedLabels = useMemo2(
684
+ () => ({ ...DEFAULT_CHART_ARTIFACT_LABELS, ...labels ?? {} }),
685
+ [labels]
686
+ );
687
+ const data = useChartArtifactData(
688
+ validConfig ? presignedUrl : null,
689
+ validConfig ? artifact.config : { variant: "LINE", series: [], xKey: "x" }
690
+ );
691
+ const { onDownload, onDownloadPng, onCopyAsImage } = callbacks ?? {};
692
+ const handleDownloadCsvSelect = onDownload ? () => onDownload(artifact) : void 0;
693
+ const handleDownloadPngSelect = onDownloadPng ? () => onDownloadPng(artifact) : void 0;
694
+ const handleCopyAsImageClick = onCopyAsImage ? () => onCopyAsImage(artifact) : void 0;
695
+ const canCopyAsImage = data.status === "ready" && data.rows.length > 0;
696
+ const canDownloadPng = data.status === "ready" && data.rows.length > 0;
697
+ const isCsvDownloadAvailable = presignedUrl !== null && isSafeDownloadUrl(presignedUrl);
698
+ const showDownloadMenu = validConfig && canDownloadPng || isCsvDownloadAvailable;
699
+ const showActions = validConfig || showDownloadMenu;
700
+ const pngFilename = (artifact.title ?? "").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("-") || "chart";
701
+ return /* @__PURE__ */ jsxs4(Artifact, { ref: cardRef, className: "shadow-none w-full rounded-2xl bg-background/70", children: [
702
+ /* @__PURE__ */ jsxs4(ArtifactHeader, { className: "bg-background/70", children: [
703
+ artifact.title ? /* @__PURE__ */ jsx11(ArtifactTitle, { className: TITLE_CLASSES, children: artifact.title }) : /* @__PURE__ */ jsx11("span", {}),
704
+ showActions ? /* @__PURE__ */ jsxs4(ArtifactActions, { ...{ [CAPTURE_EXCLUDE_ATTRIBUTE]: "" }, children: [
705
+ validConfig ? /* @__PURE__ */ jsx11(
706
+ ArtifactCopyAsImageButton,
707
+ {
708
+ targetRef: cardRef,
709
+ label: resolvedLabels.copyAsImage,
710
+ disabled: !canCopyAsImage,
711
+ onClick: handleCopyAsImageClick
712
+ }
713
+ ) : null,
714
+ showDownloadMenu ? /* @__PURE__ */ jsx11(
715
+ ArtifactDownloadMenu,
716
+ {
717
+ triggerLabel: resolvedLabels.download,
718
+ actions: {
719
+ csv: isCsvDownloadAvailable ? {
720
+ href: presignedUrl ?? "",
721
+ label: resolvedLabels.downloadCsv,
722
+ onSelect: handleDownloadCsvSelect
723
+ } : void 0,
724
+ png: validConfig ? {
725
+ targetRef: cardRef,
726
+ filename: pngFilename,
727
+ label: resolvedLabels.downloadPng,
728
+ disabled: !canDownloadPng,
729
+ onSelect: handleDownloadPngSelect
730
+ } : void 0
731
+ }
732
+ }
733
+ ) : null
734
+ ] }) : null
735
+ ] }),
736
+ /* @__PURE__ */ jsx11(
737
+ ArtifactContent,
738
+ {
739
+ className: "p-4 overflow-visible flex flex-none items-center justify-center",
740
+ style: { height: CHART_HEIGHT },
741
+ children: renderBody(validConfig, artifact, data, resolvedLabels)
742
+ }
743
+ )
744
+ ] });
745
+ }
746
+ function renderBody(validConfig, artifact, data, labels) {
747
+ if (!validConfig) {
748
+ return /* @__PURE__ */ jsx11(StatusMessage, { $tone: "info", children: labels.unavailable });
749
+ }
750
+ if (data.status === "error") {
751
+ return /* @__PURE__ */ jsx11(StatusMessage, { $tone: "error", children: labels.loadError });
752
+ }
753
+ if (data.status === "unavailable") {
754
+ return /* @__PURE__ */ jsx11(StatusMessage, { $tone: "info", children: labels.unavailable });
755
+ }
756
+ if (data.status === "loading") {
757
+ return /* @__PURE__ */ jsx11(StatusMessage, { $tone: "info", children: labels.loading });
758
+ }
759
+ if (data.rows.length === 0) {
760
+ return /* @__PURE__ */ jsx11(StatusMessage, { $tone: "info", children: labels.empty });
761
+ }
762
+ return /* @__PURE__ */ jsx11(ChartArtifactContent, { config: artifact.config, rows: data.rows, labels });
763
+ }
764
+
765
+ // src/components/artifacts/table-artifact/table-artifact.tsx
766
+ import { useEffect as useEffect4, useMemo as useMemo3 } from "react";
767
+
768
+ // src/components/artifacts/table-artifact/table-artifact-content.constants.ts
769
+ var TABLE_STATUS = {
770
+ LOADING: "loading",
771
+ READY: "ready",
772
+ ERROR: "error",
773
+ UNAVAILABLE: "unavailable"
774
+ };
775
+
776
+ // src/components/artifacts/table-artifact/table-artifact-content.utils.ts
777
+ function formatCell(row, column) {
778
+ return row[column.key] ?? "";
779
+ }
780
+ var HEAD_CLASS = "h-auto py-2 px-4 text-xs font-medium text-foreground whitespace-nowrap text-left";
781
+ var CELL_CLASS = "p-4 text-sm font-normal text-foreground align-top min-w-[150px] text-left";
782
+ var ROW_CLASSES = "border-b-brown-30 bg-background/70 hover:bg-secondary";
783
+ var DATA_ROW_CLASSES = `${ROW_CLASSES} animate-in fade-in duration-300`;
784
+ var SCROLL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.01), 0 6px 12px -2px rgb(0 0 0 / 0.03)";
785
+
786
+ // src/components/artifacts/table-artifact/table-artifact-content.tsx
787
+ import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
788
+ function TableArtifactContent({
789
+ columns,
790
+ data,
791
+ knownRowCount,
792
+ maxShimmerRows,
793
+ maxBodyHeight,
794
+ labels
795
+ }) {
796
+ if (data.status === TABLE_STATUS.ERROR) {
797
+ return /* @__PURE__ */ jsx12(StatusMessage, { $tone: "error", children: labels.loadError });
798
+ }
799
+ if (data.status === TABLE_STATUS.UNAVAILABLE) {
800
+ return /* @__PURE__ */ jsx12(StatusMessage, { $tone: "info", children: labels.unavailable });
801
+ }
802
+ if (data.status === TABLE_STATUS.LOADING) {
803
+ if (typeof knownRowCount === "number" && knownRowCount > 0) {
804
+ const shimmerCount = Math.min(knownRowCount, maxShimmerRows);
805
+ return /* @__PURE__ */ jsx12(ArtifactTable, { columns, maxBodyHeight, children: renderShimmerRows(columns, shimmerCount) });
806
+ }
807
+ return /* @__PURE__ */ jsx12(StatusMessage, { $tone: "info", children: labels.loading });
808
+ }
809
+ return /* @__PURE__ */ jsx12(ArtifactTable, { columns, maxBodyHeight, children: data.rows.length === 0 ? renderEmptyRow(columns.length, labels.empty) : renderDataRows(columns, data.rows) });
810
+ }
811
+ function ArtifactTable({ columns, maxBodyHeight, children }) {
812
+ const { targetRef, isScrolled } = useScrollShadow();
813
+ return /* @__PURE__ */ jsx12("div", { ref: targetRef, className: "overscroll-none", style: { maxHeight: maxBodyHeight, overflowY: "auto" }, children: /* @__PURE__ */ jsxs5(Table, { containerClassName: "overflow-visible", children: [
814
+ /* @__PURE__ */ jsx12(
815
+ TableHeader,
816
+ {
817
+ "data-scrolled": isScrolled ? "true" : void 0,
818
+ className: "sticky top-0 z-10 bg-background/70 backdrop-blur-lg transition-shadow",
819
+ style: isScrolled ? { boxShadow: SCROLL_SHADOW } : void 0,
820
+ children: /* @__PURE__ */ jsx12(TableRow, { className: "hover:bg-transparent", children: columns.map((column) => /* @__PURE__ */ jsx12(TableHead, { scope: "col", className: HEAD_CLASS, children: column.label }, column.key)) })
821
+ }
822
+ ),
823
+ /* @__PURE__ */ jsx12(TableBody, { children })
824
+ ] }) });
825
+ }
826
+ function renderShimmerRows(columns, count) {
827
+ return Array.from({ length: count }).map((_, index) => /* @__PURE__ */ jsx12(TableRow, { className: ROW_CLASSES, "aria-hidden": "true", children: columns.map((column) => /* @__PURE__ */ jsx12(TableCell, { className: CELL_CLASS, children: /* @__PURE__ */ jsx12("span", { className: "block h-3 w-3/4 animate-pulse rounded bg-muted-foreground/20" }) }, column.key)) }, `shimmer-${index}`));
828
+ }
829
+ function renderDataRows(columns, rows) {
830
+ return rows.map((row, index) => /* @__PURE__ */ jsx12(TableRow, { className: DATA_ROW_CLASSES, children: columns.map((column) => /* @__PURE__ */ jsx12(TableCell, { className: CELL_CLASS, children: formatCell(row, column) }, column.key)) }, index));
831
+ }
832
+ function renderEmptyRow(colSpan, emptyMessage) {
833
+ return /* @__PURE__ */ jsx12(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx12(TableCell, { colSpan, className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }) });
834
+ }
835
+
836
+ // src/components/artifacts/table-artifact/use-table-artifact-data.ts
837
+ function useTableArtifactData(presignedUrl, columns) {
838
+ return useRowsArtifactData(presignedUrl, columns, parseCsv);
839
+ }
840
+
841
+ // src/components/artifacts/table-artifact/validate.ts
842
+ function isValidTableConfig(config) {
843
+ if (!config || typeof config !== "object") return false;
844
+ const candidate = config;
845
+ if (!Array.isArray(candidate.columns)) return false;
846
+ if (candidate.columns.length === 0) return false;
847
+ return candidate.columns.every(isValidTableColumn);
848
+ }
849
+ function isValidTableColumn(column) {
850
+ if (!column || typeof column !== "object") return false;
851
+ const candidate = column;
852
+ if (typeof candidate.key !== "string" || candidate.key.length === 0) return false;
853
+ if (typeof candidate.label !== "string") return false;
854
+ return true;
855
+ }
856
+
857
+ // src/components/artifacts/table-artifact/table-artifact.tsx
858
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
859
+ var TITLE_CLASSES2 = "text-sm font-semibold text-foreground";
860
+ var TABLE_MAX_BODY_HEIGHT = 480;
861
+ var MAX_SHIMMER_ROWS = 10;
862
+ function TableArtifact({ artifact, labels, callbacks }) {
863
+ const validConfig = isValidTableConfig(artifact.config);
864
+ const validColumns = validConfig ? artifact.config.columns : null;
865
+ useEffect4(() => {
866
+ if (validConfig) return;
867
+ console.warn("[clay] TableArtifact: malformed `config` \u2014 skipping render.");
868
+ }, [validConfig]);
869
+ const resolvedLabels = useMemo3(
870
+ () => ({ ...DEFAULT_TABLE_ARTIFACT_LABELS, ...labels ?? {} }),
871
+ [labels]
872
+ );
873
+ const data = useTableArtifactData(
874
+ validConfig ? artifact.protectedAsset?.presignedUrl ?? null : null,
875
+ validColumns ?? []
876
+ );
877
+ const presignedUrl = artifact.protectedAsset?.presignedUrl ?? null;
878
+ const onDownload = callbacks?.onDownload;
879
+ const handleDownloadClick = onDownload ? () => onDownload(artifact) : void 0;
880
+ if (!validConfig || !validColumns) {
881
+ return /* @__PURE__ */ jsxs6(Artifact, { className: "shadow-none w-full rounded-2xl bg-background/70", children: [
882
+ /* @__PURE__ */ jsxs6(ArtifactHeader, { className: "bg-background/70", children: [
883
+ artifact.title ? /* @__PURE__ */ jsx13(ArtifactTitle, { className: TITLE_CLASSES2, children: artifact.title }) : /* @__PURE__ */ jsx13("span", {}),
884
+ presignedUrl ? /* @__PURE__ */ jsx13(ArtifactActions, { children: /* @__PURE__ */ jsx13(
885
+ ArtifactDownloadButton,
886
+ {
887
+ href: presignedUrl,
888
+ label: resolvedLabels.download,
889
+ onClick: handleDownloadClick
890
+ }
891
+ ) }) : null
892
+ ] }),
893
+ /* @__PURE__ */ jsx13(ArtifactContent, { className: "p-0 overflow-auto", style: { maxHeight: TABLE_MAX_BODY_HEIGHT }, children: /* @__PURE__ */ jsx13(StatusMessage, { $tone: "info", children: resolvedLabels.unavailable }) })
894
+ ] });
895
+ }
896
+ return (
897
+ // The table body owns its own scroll container (`ArtifactTable` inside `TableArtifactContent`).
898
+ // Disable the outer overflow container so only one scroll context exists — this prevents
899
+ // double-scroll nesting and lets `useScrollShadow` find the correct scroll ancestor.
900
+ /* @__PURE__ */ jsxs6(Artifact, { className: "shadow-none w-full rounded-2xl bg-background/70", children: [
901
+ /* @__PURE__ */ jsxs6(ArtifactHeader, { className: "bg-background/70", children: [
902
+ artifact.title ? /* @__PURE__ */ jsx13(ArtifactTitle, { className: TITLE_CLASSES2, children: artifact.title }) : /* @__PURE__ */ jsx13("span", {}),
903
+ /* @__PURE__ */ jsx13(ArtifactActions, { children: /* @__PURE__ */ jsx13(
904
+ ArtifactDownloadButton,
905
+ {
906
+ href: presignedUrl ?? "",
907
+ label: resolvedLabels.download,
908
+ onClick: handleDownloadClick
909
+ }
910
+ ) })
911
+ ] }),
912
+ /* @__PURE__ */ jsx13(ArtifactContent, { className: "p-0 overflow-visible", style: { maxHeight: "none" }, children: /* @__PURE__ */ jsx13(
913
+ TableArtifactContent,
914
+ {
915
+ columns: validColumns,
916
+ data,
917
+ knownRowCount: artifact.config.stats?.rowCount,
918
+ maxShimmerRows: MAX_SHIMMER_ROWS,
919
+ maxBodyHeight: TABLE_MAX_BODY_HEIGHT,
920
+ labels: resolvedLabels
921
+ }
922
+ ) })
923
+ ] })
924
+ );
925
+ }
926
+
927
+ // src/components/artifacts/chart-artifact/to-clipboard-text.ts
928
+ function chartArtifactToClipboardText(artifact) {
929
+ const trimmedTitle = artifact.title?.trim();
930
+ return trimmedTitle || "Chart";
931
+ }
932
+
933
+ // src/components/artifacts/artifact-services/chart-artifact-service.ts
934
+ var ChartArtifactService = {
935
+ type: artifactTypes.CHART,
936
+ Component: ChartArtifact,
937
+ toClipboardText: chartArtifactToClipboardText
938
+ };
939
+
940
+ // src/components/artifacts/table-artifact/to-clipboard-text.ts
941
+ function tableArtifactToClipboardText(artifact) {
942
+ const trimmedTitle = artifact.title?.trim();
943
+ return trimmedTitle || "Table";
944
+ }
945
+
946
+ // src/components/artifacts/artifact-services/table-artifact-service.ts
947
+ var TableArtifactService = {
948
+ type: artifactTypes.TABLE,
949
+ Component: TableArtifact,
950
+ toClipboardText: tableArtifactToClipboardText
951
+ };
952
+
953
+ // src/components/artifacts/artifact-services/index.ts
954
+ var ArtifactServices = {
955
+ [artifactTypes.TABLE]: TableArtifactService,
956
+ [artifactTypes.CHART]: ChartArtifactService
957
+ };
958
+ function artifactToClipboardText(artifact) {
959
+ const entry = ArtifactServices[artifact.type];
960
+ if (!entry) {
961
+ return `[Artifact: ${artifact.title ?? artifact.type}]`;
962
+ }
963
+ return entry.toClipboardText(artifact);
964
+ }
965
+
966
+ export {
967
+ artifactTypes,
968
+ DEFAULT_CHART_ARTIFACT_LABELS,
969
+ DEFAULT_TABLE_ARTIFACT_LABELS,
970
+ IconCheck,
971
+ IconCopy,
972
+ IconThumbDown,
973
+ IconThumbUp,
974
+ ArtifactCopyAsImageButton,
975
+ ArtifactDownloadButton,
976
+ isSafeDownloadUrl,
977
+ ArtifactDownloadMenu,
978
+ ChartArtifact,
979
+ TableArtifact,
980
+ ArtifactServices,
981
+ artifactToClipboardText
982
+ };
983
+ /*! Bundled license information:
984
+
985
+ @tabler/icons-react/dist/esm/defaultAttributes.mjs:
986
+ @tabler/icons-react/dist/esm/createReactComponent.mjs:
987
+ @tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs:
988
+ @tabler/icons-react/dist/esm/icons/IconCheck.mjs:
989
+ @tabler/icons-react/dist/esm/icons/IconCopy.mjs:
990
+ @tabler/icons-react/dist/esm/icons/IconDownload.mjs:
991
+ @tabler/icons-react/dist/esm/icons/IconFileSpreadsheet.mjs:
992
+ @tabler/icons-react/dist/esm/icons/IconLoader2.mjs:
993
+ @tabler/icons-react/dist/esm/icons/IconPhoto.mjs:
994
+ @tabler/icons-react/dist/esm/icons/IconThumbDown.mjs:
995
+ @tabler/icons-react/dist/esm/icons/IconThumbUp.mjs:
996
+ @tabler/icons-react/dist/esm/tabler-icons-react.mjs:
997
+ (**
998
+ * @license @tabler/icons-react v3.37.1 - MIT
999
+ *
1000
+ * This source code is licensed under the MIT license.
1001
+ * See the LICENSE file in the root directory of this source tree.
1002
+ *)
1003
+ */
1004
+ //# sourceMappingURL=chunk-DT44KNCV.js.map