@ctrl-ui/react 0.8.6 → 0.8.7

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 (152) hide show
  1. package/dist/action-bar.js +1 -1
  2. package/dist/activity.js +1 -1
  3. package/dist/blocks/coding-agent.js +16 -19
  4. package/dist/blocks/design-canvas-data.d.ts +32 -0
  5. package/dist/blocks/design-canvas-data.js +100 -0
  6. package/dist/blocks/design-canvas-inspector.d.ts +8 -0
  7. package/dist/blocks/design-canvas-inspector.js +238 -0
  8. package/dist/blocks/design-canvas.d.ts +17 -0
  9. package/dist/blocks/design-canvas.js +346 -0
  10. package/dist/blocks/file-explorer.js +2 -2
  11. package/dist/blocks/settings.js +2 -2
  12. package/dist/chat-composer-attachment.d.ts +9 -3
  13. package/dist/chat-composer-attachment.js +27 -10
  14. package/dist/chat-composer-editor.js +20 -4
  15. package/dist/chat-composer.d.ts +1 -1
  16. package/dist/chat-composer.js +4 -3
  17. package/dist/chat-layout.js +32 -19
  18. package/dist/context.js +1 -1
  19. package/dist/dynamic-notification.js +1 -1
  20. package/dist/environment-variables.js +1 -1
  21. package/dist/filter-bar/context.d.ts +32 -0
  22. package/dist/filter-bar/context.js +11 -0
  23. package/dist/filter-bar/editor.d.ts +14 -0
  24. package/dist/filter-bar/editor.js +204 -0
  25. package/dist/filter-bar/model.d.ts +9 -0
  26. package/dist/filter-bar/model.js +48 -0
  27. package/dist/filter-bar/motion.d.ts +10 -0
  28. package/dist/filter-bar/motion.js +84 -0
  29. package/dist/filter-bar/parts.d.ts +19 -0
  30. package/dist/filter-bar/parts.js +233 -0
  31. package/dist/filter-bar/types.d.ts +53 -0
  32. package/dist/filter-bar/types.js +0 -0
  33. package/dist/filter-bar.d.ts +27 -0
  34. package/dist/filter-bar.js +304 -0
  35. package/dist/hooks/use-chat-composer.d.ts +3 -1
  36. package/dist/hooks/use-chat-composer.js +2 -2
  37. package/dist/hooks/use-dropzone.d.ts +7 -4
  38. package/dist/hooks/use-dropzone.js +34 -3
  39. package/dist/inline-attachment.js +8 -14
  40. package/dist/inline-citation.js +1 -1
  41. package/dist/knob-contracts/chat-layout-knobs.d.ts +1 -1
  42. package/dist/knob-contracts/chat-layout-knobs.js +2 -1
  43. package/dist/knob-contracts/choice-knobs.d.ts +1 -1
  44. package/dist/knob-contracts/choice-knobs.js +1 -0
  45. package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
  46. package/dist/knob-contracts/collapsible-knobs.js +1 -1
  47. package/dist/knob-contracts/filter-bar-knobs.d.ts +4 -0
  48. package/dist/knob-contracts/filter-bar-knobs.js +16 -0
  49. package/dist/knob-contracts/infinite-canvas-knobs.d.ts +1 -1
  50. package/dist/knob-contracts/infinite-canvas-knobs.js +3 -0
  51. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  52. package/dist/knob-contracts/page-layout-knobs.js +3 -1
  53. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  54. package/dist/knob-contracts/popup-knobs.js +1 -0
  55. package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
  56. package/dist/knob-contracts/table-of-contents-knobs.js +3 -5
  57. package/dist/lib/phone-input-format.js +1 -1
  58. package/dist/markdown-block.js +11 -7
  59. package/dist/scripts/contrast-eval.js +5 -4
  60. package/dist/styles/index.css +5 -4
  61. package/dist/styles/recipes/button-group.css +1 -1
  62. package/dist/styles/recipes/button.css +2 -2
  63. package/dist/styles/recipes/chat-composer.css +14 -6
  64. package/dist/styles/recipes/chat-layout.css +29 -2
  65. package/dist/styles/recipes/choice.css +10 -3
  66. package/dist/styles/recipes/code-diff.css +16 -4
  67. package/dist/styles/recipes/code.css +11 -5
  68. package/dist/styles/recipes/collapsible.css +37 -0
  69. package/dist/styles/recipes/color-picker.css +12 -2
  70. package/dist/styles/recipes/dockable-panel.css +11 -3
  71. package/dist/styles/recipes/field-parts.css +62 -0
  72. package/dist/styles/recipes/field.css +28 -6
  73. package/dist/styles/recipes/filter-bar.css +174 -0
  74. package/dist/styles/recipes/infinite-canvas.css +51 -3
  75. package/dist/styles/recipes/inline-attachment.css +6 -1
  76. package/dist/styles/recipes/page-layout.css +18 -0
  77. package/dist/styles/recipes/pagination.css +1 -1
  78. package/dist/styles/recipes/popup-controls.css +16 -0
  79. package/dist/styles/recipes/popup-drawer.css +34 -32
  80. package/dist/styles/recipes/popup-lists.css +11 -2
  81. package/dist/styles/recipes/popup.css +10 -12
  82. package/dist/styles/recipes/range.css +1 -1
  83. package/dist/styles/recipes/resizable.css +34 -0
  84. package/dist/styles/recipes/spinner.css +4 -0
  85. package/dist/styles/recipes/table-of-contents.css +42 -42
  86. package/dist/styles/recipes/tabs.css +21 -0
  87. package/dist/styles/recipes/track-highlight.css +2 -1
  88. package/dist/styles/recipes/transcript-divider.css +2 -2
  89. package/dist/styles/theme.css +250 -91
  90. package/dist/ui/alert-dialog.js +1 -1
  91. package/dist/ui/autocomplete.js +5 -1
  92. package/dist/ui/button.js +2 -2
  93. package/dist/ui/checkbox-group.js +1 -1
  94. package/dist/ui/code-diff.js +1 -1
  95. package/dist/ui/code.js +3 -3
  96. package/dist/ui/collapsible.js +1 -1
  97. package/dist/ui/color-picker.js +1 -1
  98. package/dist/ui/combobox-disabled-selection.d.ts +1 -1
  99. package/dist/ui/combobox-disabled-selection.js +2 -1
  100. package/dist/ui/combobox.d.ts +5 -23
  101. package/dist/ui/combobox.js +26 -14
  102. package/dist/ui/command.js +9 -1
  103. package/dist/ui/context-menu.js +1 -1
  104. package/dist/ui/dialog.js +1 -1
  105. package/dist/ui/dockable-panel.js +18 -10
  106. package/dist/ui/drawer.d.ts +7 -4
  107. package/dist/ui/drawer.js +36 -23
  108. package/dist/ui/dropdown-menu.js +1 -1
  109. package/dist/ui/dropzone.d.ts +3 -1
  110. package/dist/ui/dropzone.js +3 -1
  111. package/dist/ui/field.js +1 -1
  112. package/dist/ui/hover-card.js +1 -1
  113. package/dist/ui/infinite-canvas.d.ts +11 -0
  114. package/dist/ui/infinite-canvas.js +82 -7
  115. package/dist/ui/input-group.d.ts +2 -1
  116. package/dist/ui/input-group.js +12 -3
  117. package/dist/ui/input.js +9 -0
  118. package/dist/ui/item.js +1 -1
  119. package/dist/ui/markdown-elements.js +11 -8
  120. package/dist/ui/menubar.js +1 -1
  121. package/dist/ui/morphing-panel.js +1 -1
  122. package/dist/ui/navigation-menu.js +1 -1
  123. package/dist/ui/number-field.d.ts +1 -1
  124. package/dist/ui/number-field.js +13 -2
  125. package/dist/ui/phone-input.js +2 -2
  126. package/dist/ui/popover.js +1 -1
  127. package/dist/ui/radio-group.d.ts +1 -0
  128. package/dist/ui/radio-group.js +11 -1
  129. package/dist/ui/resizable.d.ts +18 -1
  130. package/dist/ui/resizable.js +127 -7
  131. package/dist/ui/responsive-dialog.js +6 -3
  132. package/dist/ui/rich-tooltip.js +1 -1
  133. package/dist/ui/scroll-area.d.ts +4 -1
  134. package/dist/ui/scroll-area.js +2 -1
  135. package/dist/ui/select.d.ts +2 -1
  136. package/dist/ui/select.js +58 -33
  137. package/dist/ui/sheet.js +1 -1
  138. package/dist/ui/sidebar-menu.js +1 -1
  139. package/dist/ui/sidebar-provider.js +2 -2
  140. package/dist/ui/sidebar-resize-rail.js +1 -1
  141. package/dist/ui/sidebar.js +1 -1
  142. package/dist/ui/table-of-contents.d.ts +4 -3
  143. package/dist/ui/table-of-contents.js +126 -47
  144. package/dist/ui/table.js +11 -7
  145. package/dist/ui/toast.js +1 -1
  146. package/dist/ui/toggle.d.ts +1 -1
  147. package/dist/ui/toggle.js +2 -1
  148. package/dist/ui/tooltip.js +1 -1
  149. package/dist/ui/tree.js +1 -1
  150. package/dist/ui/trigger-menu.js +7 -3
  151. package/dist/user-ask.js +1 -1
  152. package/package.json +1 -1
@@ -0,0 +1,346 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { Button } from "../ui/button.js";
4
+ import { ScrollArea } from "../ui/scroll-area.js";
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip.js";
6
+ import { InfiniteCanvas, InfiniteCanvasContent, InfiniteCanvasControls, InfiniteCanvasItem } from "../ui/infinite-canvas.js";
7
+ import { ResizableFloatingPanel } from "../ui/resizable.js";
8
+ import { Toolbar, ToolbarButton, ToolbarGroup } from "../ui/toolbar.js";
9
+ import { createDesignCanvasLayer, designCanvasFillColor, drawnDesignCanvasBounds } from "./design-canvas-data.js";
10
+ import { DesignCanvasInspector } from "./design-canvas-inspector.js";
11
+ import { useRef, useState } from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ import { CircleIcon, FrameIcon, MousePointer2Icon, SquareIcon, TypeIcon } from "lucide-react";
14
+ //#region src/blocks/design-canvas.tsx
15
+ const TOOLS = [
16
+ {
17
+ tool: "move",
18
+ label: "Move",
19
+ shortcut: "V",
20
+ icon: /* @__PURE__ */ jsx(MousePointer2Icon, {})
21
+ },
22
+ {
23
+ tool: "frame",
24
+ label: "Frame",
25
+ shortcut: "F",
26
+ icon: /* @__PURE__ */ jsx(FrameIcon, {})
27
+ },
28
+ {
29
+ tool: "rectangle",
30
+ label: "Rectangle",
31
+ shortcut: "R",
32
+ icon: /* @__PURE__ */ jsx(SquareIcon, {})
33
+ },
34
+ {
35
+ tool: "ellipse",
36
+ label: "Ellipse",
37
+ shortcut: "O",
38
+ icon: /* @__PURE__ */ jsx(CircleIcon, {})
39
+ },
40
+ {
41
+ tool: "text",
42
+ label: "Text",
43
+ shortcut: "T",
44
+ icon: /* @__PURE__ */ jsx(TypeIcon, {})
45
+ }
46
+ ];
47
+ const LAYER_ICONS = {
48
+ frame: /* @__PURE__ */ jsx(FrameIcon, {}),
49
+ rectangle: /* @__PURE__ */ jsx(SquareIcon, {}),
50
+ ellipse: /* @__PURE__ */ jsx(CircleIcon, {}),
51
+ text: /* @__PURE__ */ jsx(TypeIcon, {})
52
+ };
53
+ const NUDGE_DIRECTIONS = {
54
+ ArrowLeft: {
55
+ x: -1,
56
+ y: 0
57
+ },
58
+ ArrowRight: {
59
+ x: 1,
60
+ y: 0
61
+ },
62
+ ArrowUp: {
63
+ x: 0,
64
+ y: -1
65
+ },
66
+ ArrowDown: {
67
+ x: 0,
68
+ y: 1
69
+ }
70
+ };
71
+ const DEFAULT_TRANSFORM = {
72
+ x: 0,
73
+ y: 0,
74
+ scale: 1
75
+ };
76
+ const CLICK_TOLERANCE_PX = 4;
77
+ function newLayerId(kind) {
78
+ return `${kind}-${Math.random().toString(36).slice(2, 8)}`;
79
+ }
80
+ function toolForShortcut(key) {
81
+ return TOOLS.find((entry) => entry.shortcut === key.toUpperCase())?.tool;
82
+ }
83
+ function layerFromDraft(draft, id) {
84
+ return createDesignCanvasLayer(draft.kind, drawnDesignCanvasBounds(draft.kind, draft.start, draft.end, draft.clickTolerance), id);
85
+ }
86
+ function DesignCanvasBlock({ layers, defaultLayers = [], onLayersChange, selectedLayerId, defaultSelectedLayerId = null, onSelectedLayerIdChange, defaultTransform = DEFAULT_TRANSFORM, layout = "viewport", className, ...props }) {
87
+ const [internalLayers, setInternalLayers] = useState(defaultLayers);
88
+ const [internalSelectedLayerId, setInternalSelectedLayerId] = useState(defaultSelectedLayerId);
89
+ const transformRef = useRef(defaultTransform);
90
+ const [tool, setTool] = useState("move");
91
+ const [draft, setDraft] = useState(null);
92
+ const currentLayers = layers ?? internalLayers;
93
+ const currentSelectedLayerId = selectedLayerId === void 0 ? internalSelectedLayerId : selectedLayerId;
94
+ const selectedLayer = currentLayers.find((layer) => layer.id === currentSelectedLayerId);
95
+ function changeLayers(next) {
96
+ if (layers === void 0) setInternalLayers(next);
97
+ onLayersChange?.(next);
98
+ }
99
+ function selectLayer(layerId) {
100
+ if (selectedLayerId === void 0) setInternalSelectedLayerId(layerId);
101
+ onSelectedLayerIdChange?.(layerId);
102
+ }
103
+ function replaceLayer(next) {
104
+ changeLayers(currentLayers.map((layer) => layer.id === next.id ? next : layer));
105
+ }
106
+ function worldPointAt(event) {
107
+ const bounds = event.currentTarget.getBoundingClientRect();
108
+ const { x, y, scale } = transformRef.current;
109
+ return {
110
+ x: (event.clientX - bounds.left - x) / scale,
111
+ y: (event.clientY - bounds.top - y) / scale
112
+ };
113
+ }
114
+ function beginDrawOrDeselect(event) {
115
+ if (event.defaultPrevented || event.target !== event.currentTarget) return;
116
+ if (tool === "move") {
117
+ selectLayer(null);
118
+ return;
119
+ }
120
+ if (!event.isPrimary || event.button !== 0) return;
121
+ event.preventDefault();
122
+ event.currentTarget.setPointerCapture(event.pointerId);
123
+ const point = worldPointAt(event);
124
+ setDraft({
125
+ kind: tool,
126
+ pointerId: event.pointerId,
127
+ start: point,
128
+ end: point,
129
+ clickTolerance: CLICK_TOLERANCE_PX / transformRef.current.scale
130
+ });
131
+ }
132
+ function resizeDraft(event) {
133
+ if (draft?.pointerId !== event.pointerId) return;
134
+ setDraft({
135
+ ...draft,
136
+ end: worldPointAt(event)
137
+ });
138
+ }
139
+ function commitDraft(event) {
140
+ if (draft?.pointerId !== event.pointerId) return;
141
+ const layer = layerFromDraft(draft, newLayerId(draft.kind));
142
+ setDraft(null);
143
+ changeLayers([...currentLayers, layer]);
144
+ selectLayer(layer.id);
145
+ setTool("move");
146
+ }
147
+ function cancelDraft(event) {
148
+ if (draft?.pointerId === event.pointerId) setDraft(null);
149
+ }
150
+ function editSelectedLayerWithKey(event) {
151
+ if (!selectedLayer) return false;
152
+ const nudge = NUDGE_DIRECTIONS[event.key];
153
+ if (event.key === "Delete" || event.key === "Backspace") {
154
+ changeLayers(currentLayers.filter((layer) => layer.id !== selectedLayer.id));
155
+ selectLayer(null);
156
+ } else if (nudge) {
157
+ const distance = event.shiftKey ? 10 : 1;
158
+ replaceLayer({
159
+ ...selectedLayer,
160
+ x: selectedLayer.x + nudge.x * distance,
161
+ y: selectedLayer.y + nudge.y * distance
162
+ });
163
+ } else return false;
164
+ return true;
165
+ }
166
+ function handleShortcut(event) {
167
+ if (event.metaKey || event.ctrlKey || event.altKey) return;
168
+ const shortcutTool = toolForShortcut(event.key);
169
+ if (shortcutTool) setTool(shortcutTool);
170
+ else if (event.key === "Escape") {
171
+ setDraft(null);
172
+ setTool("move");
173
+ selectLayer(null);
174
+ } else if (!editSelectedLayerWithKey(event)) return;
175
+ event.preventDefault();
176
+ }
177
+ return /* @__PURE__ */ jsxs("div", {
178
+ className: cn("@container/design-canvas relative isolate overflow-hidden bg-background text-foreground", layout === "contained" ? "h-full min-h-0" : "h-svh", className),
179
+ ...props,
180
+ children: [
181
+ /* @__PURE__ */ jsxs(InfiniteCanvas, {
182
+ "aria-label": "Design canvas",
183
+ defaultTransform,
184
+ onTransformChange: (next) => {
185
+ transformRef.current = next;
186
+ },
187
+ "data-tool": tool,
188
+ className: cn("size-full", tool !== "move" && "cursor-crosshair"),
189
+ style: {
190
+ "--cui-infinite-canvas-radius": "0px",
191
+ "--cui-infinite-canvas-border-color": "transparent",
192
+ "--cui-infinite-canvas-background": "var(--muted)"
193
+ },
194
+ onPointerDown: beginDrawOrDeselect,
195
+ onPointerMove: resizeDraft,
196
+ onPointerUp: commitDraft,
197
+ onPointerCancel: cancelDraft,
198
+ onKeyDown: handleShortcut,
199
+ children: [/* @__PURE__ */ jsxs(InfiniteCanvasContent, {
200
+ className: cn(tool !== "move" && "[&>*]:pointer-events-none"),
201
+ children: [currentLayers.map((layer) => /* @__PURE__ */ jsx(DesignCanvasLayerNode, {
202
+ layer,
203
+ selected: layer.id === currentSelectedLayerId,
204
+ onSelect: () => selectLayer(layer.id),
205
+ onLayerChange: replaceLayer
206
+ }, layer.id)), draft ? /* @__PURE__ */ jsx(DesignCanvasLayerNode, {
207
+ layer: layerFromDraft(draft, "draft"),
208
+ selected: true
209
+ }) : null]
210
+ }), /* @__PURE__ */ jsx(InfiniteCanvasControls, { className: "top-3 right-auto bottom-auto left-3 @4xl/design-canvas:left-1/2 @4xl/design-canvas:-translate-x-1/2" })]
211
+ }),
212
+ /* @__PURE__ */ jsx(ResizableFloatingPanel, {
213
+ side: "left",
214
+ defaultSize: 224,
215
+ minSize: 180,
216
+ maxSize: 360,
217
+ className: "hidden @4xl/design-canvas:flex",
218
+ children: /* @__PURE__ */ jsxs("aside", {
219
+ "aria-label": "Layers",
220
+ className: "flex min-h-0 flex-1 flex-col",
221
+ children: [/* @__PURE__ */ jsx("h2", {
222
+ className: "flex h-11 shrink-0 items-center border-b px-3 text-caption font-medium",
223
+ children: "Layers"
224
+ }), /* @__PURE__ */ jsx(ScrollArea, {
225
+ className: "min-h-0 flex-1",
226
+ children: /* @__PURE__ */ jsx("ul", {
227
+ className: "flex flex-col gap-0.5 p-2",
228
+ children: currentLayers.toReversed().map((layer) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Button, {
229
+ variant: "ghost",
230
+ size: "sm",
231
+ active: layer.id === currentSelectedLayerId,
232
+ "aria-pressed": layer.id === currentSelectedLayerId,
233
+ className: "w-full justify-start [&_svg]:size-3.5 [&_svg]:text-muted-foreground",
234
+ onClick: () => selectLayer(layer.id),
235
+ children: [LAYER_ICONS[layer.kind], /* @__PURE__ */ jsx("span", {
236
+ className: "truncate",
237
+ children: layer.name
238
+ })]
239
+ }) }, layer.id))
240
+ })
241
+ })]
242
+ })
243
+ }),
244
+ /* @__PURE__ */ jsx(ResizableFloatingPanel, {
245
+ defaultSize: 280,
246
+ minSize: 256,
247
+ maxSize: 440,
248
+ className: "hidden @xl/design-canvas:flex",
249
+ children: /* @__PURE__ */ jsxs("aside", {
250
+ "aria-label": "Properties",
251
+ className: "flex min-h-0 flex-1 flex-col",
252
+ children: [/* @__PURE__ */ jsx("h2", {
253
+ className: "flex h-11 shrink-0 items-center border-b px-3 text-caption font-medium",
254
+ children: /* @__PURE__ */ jsx("span", {
255
+ className: "truncate",
256
+ children: selectedLayer?.name ?? "Design"
257
+ })
258
+ }), /* @__PURE__ */ jsx(ScrollArea, {
259
+ className: "min-h-0 flex-1",
260
+ children: /* @__PURE__ */ jsx(DesignCanvasInspector, {
261
+ layer: selectedLayer,
262
+ onLayerChange: replaceLayer
263
+ })
264
+ })]
265
+ })
266
+ }),
267
+ /* @__PURE__ */ jsx(DesignCanvasToolbar, {
268
+ tool,
269
+ onToolChange: setTool
270
+ })
271
+ ]
272
+ });
273
+ }
274
+ function DesignCanvasLayerNode({ layer, selected, onSelect, onLayerChange }) {
275
+ const fillColor = designCanvasFillColor(layer.fill);
276
+ return /* @__PURE__ */ jsxs(InfiniteCanvasItem, {
277
+ x: layer.x,
278
+ y: layer.y,
279
+ "aria-label": layer.name,
280
+ "data-selected": selected || void 0,
281
+ className: "outline-primary data-selected:outline-2",
282
+ style: {
283
+ width: layer.width,
284
+ height: layer.height,
285
+ rotate: `${layer.rotation}deg`,
286
+ opacity: layer.opacity / 100
287
+ },
288
+ onPointerDown: onSelect,
289
+ onPositionChange: onLayerChange && ((position) => onLayerChange({
290
+ ...layer,
291
+ x: Math.round(position.x),
292
+ y: Math.round(position.y)
293
+ })),
294
+ children: [
295
+ layer.kind === "frame" ? /* @__PURE__ */ jsx("span", {
296
+ className: "absolute bottom-full left-0 mb-1 whitespace-nowrap text-caption text-muted-foreground",
297
+ children: layer.name
298
+ }) : null,
299
+ layer.kind === "text" ? /* @__PURE__ */ jsx("p", {
300
+ className: "size-full overflow-hidden text-2xl leading-8 whitespace-nowrap",
301
+ style: { color: fillColor },
302
+ children: layer.text
303
+ }) : /* @__PURE__ */ jsx("div", {
304
+ className: "size-full",
305
+ style: {
306
+ background: fillColor,
307
+ borderRadius: layer.kind === "ellipse" ? "50%" : layer.cornerRadius
308
+ }
309
+ }),
310
+ selected ? /* @__PURE__ */ jsxs("span", {
311
+ className: "absolute top-full left-1/2 mt-1.5 -translate-x-1/2 rounded-sm bg-primary px-1 text-[10px] leading-4 whitespace-nowrap text-primary-foreground tabular-nums",
312
+ children: [
313
+ layer.width,
314
+ " × ",
315
+ layer.height
316
+ ]
317
+ }) : null
318
+ ]
319
+ });
320
+ }
321
+ function DesignCanvasToolbar({ tool, onToolChange }) {
322
+ return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(Toolbar, {
323
+ variant: "floating",
324
+ "aria-label": "Design tools",
325
+ className: "absolute bottom-4 left-4 w-max @4xl/design-canvas:left-1/2 @4xl/design-canvas:-translate-x-1/2",
326
+ children: /* @__PURE__ */ jsx(ToolbarGroup, {
327
+ "aria-label": "Tools",
328
+ children: TOOLS.map((entry) => /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
329
+ render: /* @__PURE__ */ jsx(ToolbarButton, {
330
+ "aria-label": entry.label,
331
+ iconOnly: true,
332
+ "aria-pressed": tool === entry.tool,
333
+ "data-pressed": tool === entry.tool ? "" : void 0,
334
+ onClick: () => onToolChange(entry.tool)
335
+ }),
336
+ children: entry.icon
337
+ }), /* @__PURE__ */ jsxs(TooltipContent, { children: [
338
+ entry.label,
339
+ " · ",
340
+ entry.shortcut
341
+ ] })] }, entry.tool))
342
+ })
343
+ }) });
344
+ }
345
+ //#endregion
346
+ export { DesignCanvasBlock };
@@ -3,12 +3,12 @@ import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { ScrollArea } from "../ui/scroll-area.js";
5
5
  import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group.js";
6
+ import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
6
7
  import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
7
8
  import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
8
9
  import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarRail, SidebarTrigger } from "../ui/sidebar.js";
9
- import { groupFileExplorerItems, resolveFileExplorer, searchFileExplorer } from "./file-explorer-data.js";
10
- import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
11
10
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "../ui/resizable.js";
11
+ import { groupFileExplorerItems, resolveFileExplorer, searchFileExplorer } from "./file-explorer-data.js";
12
12
  import { useState } from "react";
13
13
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
14
  import { ChevronRightIcon, FileIcon, FolderIcon, SearchIcon, XIcon } from "lucide-react";
@@ -3,12 +3,12 @@ import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { ScrollArea } from "../ui/scroll-area.js";
5
5
  import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group.js";
6
+ import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
7
+ import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "../ui/field.js";
6
8
  import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
7
9
  import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
8
10
  import { Sidebar, SidebarContent, SidebarHeader, SidebarInset, SidebarTrigger } from "../ui/sidebar.js";
9
- import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
10
11
  import { searchSettings } from "./settings-data.js";
11
- import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "../ui/field.js";
12
12
  import { useEffect, useId, useState } from "react";
13
13
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
14
  import { ArrowLeftIcon, SearchIcon, XIcon } from "lucide-react";
@@ -13,8 +13,14 @@ export type ChatComposerAttachmentsProps = ComponentProps<"div"> & {
13
13
  style?: CSSProperties & ChatComposerAttachmentKnobStyle;
14
14
  };
15
15
  export declare function ChatComposerAttachments({ label, className, viewportClassName, children, ...props }: ChatComposerAttachmentsProps): import("react").JSX.Element;
16
- export type ChatComposerAttachmentProps = Omit<ComponentProps<"li">, "style"> & {
16
+ type ChatComposerAttachmentSource = {
17
+ file: File;
18
+ name?: string;
19
+ } | {
20
+ file?: undefined;
17
21
  name: string;
22
+ };
23
+ export type ChatComposerAttachmentProps = Omit<ComponentProps<"li">, "style"> & ChatComposerAttachmentSource & {
18
24
  type?: string;
19
25
  description?: ReactNode;
20
26
  status?: ChatComposerAttachmentStatus;
@@ -26,7 +32,7 @@ export type ChatComposerAttachmentProps = Omit<ComponentProps<"li">, "style"> &
26
32
  removeLabel?: string;
27
33
  style?: CSSProperties & ChatComposerAttachmentKnobStyle;
28
34
  };
29
- export declare function ChatComposerAttachment({ name, type, description, status, progress: progressInput, previewUrl, kind, variant, onRemove, removeLabel, className, children, role, "aria-label": ariaLabel, ...props }: ChatComposerAttachmentProps): import("react").JSX.Element;
35
+ export declare function ChatComposerAttachment({ file, name: nameInput, type: typeInput, description, status, progress: progressInput, previewUrl: previewUrlInput, kind, variant, onRemove, removeLabel, className, children, "aria-label": ariaLabel, ...props }: ChatComposerAttachmentProps): import("react").JSX.Element;
30
36
  export type ChatComposerAttachmentPreviewProps = Omit<ComponentProps<"div">, "style"> & {
31
37
  style?: CSSProperties & ChatComposerAttachmentKnobStyle;
32
38
  };
@@ -50,5 +56,5 @@ export declare function ChatComposerAttachmentRemove({ className, onClick, child
50
56
  export type ChatComposerAttachmentProgressProps = Omit<ComponentProps<"div">, "style"> & {
51
57
  style?: CSSProperties & ChatComposerAttachmentKnobStyle;
52
58
  };
53
- export declare function ChatComposerAttachmentProgress({ className, children, style, ...props }: ChatComposerAttachmentProgressProps): import("react").JSX.Element | null;
59
+ export declare function ChatComposerAttachmentProgress({ className, children, ...props }: ChatComposerAttachmentProgressProps): import("react").JSX.Element | null;
54
60
  //#endregion
@@ -3,7 +3,7 @@ import { cn } from "./lib/cn.js";
3
3
  import { Button } from "./ui/button.js";
4
4
  import { ScrollArea } from "./ui/scroll-area.js";
5
5
  import { Spinner } from "./ui/spinner.js";
6
- import { createContext, useContext } from "react";
6
+ import { createContext, useContext, useLayoutEffect, useState } from "react";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { XIcon } from "lucide-react";
9
9
  //#region src/chat-composer-attachment.tsx
@@ -67,6 +67,24 @@ function kindFromAttachment(type, name) {
67
67
  ].includes(extension)) return "document";
68
68
  return "file";
69
69
  }
70
+ function resolveVariant(variant, kind, status, previewUrl) {
71
+ if (variant !== "auto") return variant;
72
+ if (status === "error") return "file";
73
+ return previewUrl || kind === "image" ? "preview" : "file";
74
+ }
75
+ function useObjectUrl(blob) {
76
+ const [objectUrl, setObjectUrl] = useState();
77
+ useLayoutEffect(() => {
78
+ if (!blob) return;
79
+ const url = URL.createObjectURL(blob);
80
+ setObjectUrl({
81
+ blob,
82
+ url
83
+ });
84
+ return () => URL.revokeObjectURL(url);
85
+ }, [blob]);
86
+ return blob && objectUrl?.blob === blob ? objectUrl.url : void 0;
87
+ }
70
88
  function progressValue(progress) {
71
89
  if (typeof progress !== "number" || Number.isNaN(progress)) return void 0;
72
90
  return Math.min(100, Math.max(0, progress));
@@ -104,7 +122,6 @@ function ChatComposerAttachments({ label = "Attachments", className, viewportCla
104
122
  "aria-label": label,
105
123
  lockAxis: "y",
106
124
  mask: true,
107
- scrollbarVisibility: "hover",
108
125
  className: "w-full",
109
126
  viewportClassName,
110
127
  children: /* @__PURE__ */ jsx("ul", {
@@ -117,11 +134,13 @@ function ChatComposerAttachments({ label = "Attachments", className, viewportCla
117
134
  })
118
135
  });
119
136
  }
120
- function ChatComposerAttachment({ name, type, description, status = "idle", progress: progressInput, previewUrl, kind, variant = "auto", onRemove, removeLabel, className, children, role, "aria-label": ariaLabel, ...props }) {
137
+ function ChatComposerAttachment({ file, name: nameInput, type: typeInput, description, status = "idle", progress: progressInput, previewUrl: previewUrlInput, kind, variant = "auto", onRemove, removeLabel, className, children, "aria-label": ariaLabel, ...props }) {
138
+ const name = nameInput ?? file?.name ?? "";
139
+ const type = typeInput ?? file?.type;
121
140
  const resolvedKind = kind ?? kindFromAttachment(type, name);
122
- let resolvedVariant;
123
- if (variant === "auto") resolvedVariant = previewUrl || resolvedKind === "image" ? "preview" : "file";
124
- else resolvedVariant = variant;
141
+ const resolvedVariant = resolveVariant(variant, resolvedKind, status, previewUrlInput);
142
+ const filePreviewUrl = useObjectUrl(resolvedVariant === "preview" && !previewUrlInput ? file : void 0);
143
+ const previewUrl = previewUrlInput ?? filePreviewUrl;
125
144
  const extension = extensionFromName(name);
126
145
  const progress = progressValue(progressInput);
127
146
  return /* @__PURE__ */ jsx(ChatComposerAttachmentContext.Provider, {
@@ -139,7 +158,6 @@ function ChatComposerAttachment({ name, type, description, status = "idle", prog
139
158
  removeLabel
140
159
  },
141
160
  children: /* @__PURE__ */ jsx("li", {
142
- role,
143
161
  "aria-label": ariaLabel ?? name,
144
162
  "data-control-ui": "chat-composer-attachment",
145
163
  "data-control-family": "chat-composer-attachment",
@@ -166,7 +184,7 @@ function ChatComposerAttachmentPreview({ className, children, ...props }) {
166
184
  "data-control-ui": "chat-composer-attachment",
167
185
  "data-control-family": "chat-composer-attachment",
168
186
  "data-slot": "preview",
169
- className: cn("relative grid shrink-0 place-items-center overflow-hidden", className),
187
+ className: cn("relative flex shrink-0 items-center justify-center overflow-hidden", className),
170
188
  ...props,
171
189
  children: children ?? (context.previewUrl ? /* @__PURE__ */ jsx("img", {
172
190
  src: context.previewUrl,
@@ -239,7 +257,7 @@ function ChatComposerAttachmentRemove({ className, onClick, children, "aria-labe
239
257
  })
240
258
  });
241
259
  }
242
- function ChatComposerAttachmentProgress({ className, children, style, ...props }) {
260
+ function ChatComposerAttachmentProgress({ className, children, ...props }) {
243
261
  const { name, status, progress } = useChatComposerAttachmentContext();
244
262
  if (status !== "uploading") return null;
245
263
  return /* @__PURE__ */ jsx("div", {
@@ -252,7 +270,6 @@ function ChatComposerAttachmentProgress({ className, children, style, ...props }
252
270
  "data-control-family": "chat-composer-attachment",
253
271
  "data-slot": "progress",
254
272
  className: cn("pointer-events-none absolute inset-0 z-1 grid place-items-center", className),
255
- style,
256
273
  ...props,
257
274
  children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
258
275
  "data-control-ui": "chat-composer-attachment",
@@ -13,6 +13,19 @@ import { EditorState, Plugin } from "prosemirror-state";
13
13
  import { EditorView } from "prosemirror-view";
14
14
  //#region src/chat-composer-editor.tsx
15
15
  const SUBMIT_KEY = "Enter";
16
+ function releaseFileDrag(_view, event) {
17
+ return event.dataTransfer ? Array.from(event.dataTransfer.types).includes("Files") : false;
18
+ }
19
+ /** ProseMirror claims every drag and paste over the editor. A transfer it cannot insert is not editor input, so it has to reach the surrounding drop zone with its default intact. */
20
+ const fileTransferPassthrough = {
21
+ dragenter: releaseFileDrag,
22
+ dragover: releaseFileDrag,
23
+ drop: releaseFileDrag,
24
+ paste: (_view, event) => {
25
+ const types = event.clipboardData ? Array.from(event.clipboardData.types) : [];
26
+ return types.includes("Files") && !types.includes("text/plain");
27
+ }
28
+ };
16
29
  function ChatComposerEditor({ className, placeholder, style, extensions = [] }) {
17
30
  const input = useChatComposerContext();
18
31
  const mountRef = useRef(null);
@@ -59,10 +72,13 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
59
72
  schema,
60
73
  doc: docFromText(schema, inputRef.current.value),
61
74
  plugins: [
62
- new Plugin({ props: { handleKeyDown: (_view, event) => {
63
- for (const handler of keyHandlersRef.current) if (handler(event)) return true;
64
- return false;
65
- } } }),
75
+ new Plugin({ props: {
76
+ handleKeyDown: (_view, event) => {
77
+ for (const handler of keyHandlersRef.current) if (handler(event)) return true;
78
+ return false;
79
+ },
80
+ handleDOMEvents: fileTransferPassthrough
81
+ } }),
66
82
  ...extensionPlugins,
67
83
  history(),
68
84
  keymap(extensionKeymap),
@@ -29,7 +29,7 @@ export declare function useChatComposerContext(): {
29
29
  preventDefault: () => void;
30
30
  }) => void;
31
31
  };
32
- export declare function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, className, children, ...props }: ChatComposerProps): import("react").JSX.Element;
32
+ export declare function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, allowEmptySubmit, className, children, ...props }: ChatComposerProps): import("react").JSX.Element;
33
33
  export type ChatComposerShellProps = Omit<ComponentProps<"div">, "style"> & {
34
34
  style?: CSSProperties & ChatComposerKnobStyle;
35
35
  };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { cn } from "./lib/cn.js";
3
2
  import { hasSkinAdornment, skinAdornment } from "./skin.js";
3
+ import { cn } from "./lib/cn.js";
4
4
  import { useSkin } from "./skin-provider.js";
5
5
  import { Button } from "./ui/button.js";
6
6
  import { useChatComposer } from "./hooks/use-chat-composer.js";
@@ -14,7 +14,7 @@ function useChatComposerContext() {
14
14
  if (!context) throw new Error("ChatComposer compound components must be rendered inside <ChatComposer>.");
15
15
  return context;
16
16
  }
17
- function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, className, children, ...props }) {
17
+ function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, allowEmptySubmit = false, className, children, ...props }) {
18
18
  const skin = useSkin();
19
19
  const input = useChatComposer({
20
20
  value,
@@ -24,6 +24,7 @@ function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "i
24
24
  state,
25
25
  density,
26
26
  disabled,
27
+ allowEmptySubmit,
27
28
  trackSends: hasSkinAdornment(skin, "chat-composer", "send-layer")
28
29
  });
29
30
  const sendLayer = skinAdornment(skin, "chat-composer", "send-layer", { sendCount: input.sendCount });
@@ -117,7 +118,7 @@ function ChatComposerFooter({ className, ...props }) {
117
118
  "data-control-ui": "chat-composer",
118
119
  "data-control-family": "chat-composer",
119
120
  "data-slot": "footer",
120
- className: cn(className),
121
+ className,
121
122
  ...props
122
123
  });
123
124
  }
@@ -2,11 +2,16 @@
2
2
  import { cn } from "./lib/cn.js";
3
3
  import { SkinAdornment } from "./skin-provider.js";
4
4
  import { Button } from "./ui/button.js";
5
+ import { ScrollArea } from "./ui/scroll-area.js";
5
6
  import { useChatThreadScroll } from "./hooks/use-chat-thread-scroll.js";
6
7
  import { createContext, useContext } from "react";
7
8
  import { jsx, jsxs } from "react/jsx-runtime";
8
9
  import { ArrowDown } from "lucide-react";
9
10
  //#region src/chat-layout.tsx
11
+ const chromeSizing = {
12
+ panel: "mx-auto min-h-[640px] max-w-3xl",
13
+ embedded: "h-full min-h-0 flex-1"
14
+ };
10
15
  function ChatLayout({ children, chrome = "panel", className, ...props }) {
11
16
  return /* @__PURE__ */ jsxs("section", {
12
17
  "data-control-ui": "chat-layout",
@@ -14,7 +19,7 @@ function ChatLayout({ children, chrome = "panel", className, ...props }) {
14
19
  "data-slot": "root",
15
20
  "data-chrome": chrome,
16
21
  "data-surface": chrome === "panel" ? "panel" : void 0,
17
- className: cn("relative mx-auto flex min-h-[640px] w-full max-w-3xl flex-col overflow-hidden", className),
22
+ className: cn("relative flex w-full flex-col overflow-hidden", chromeSizing[chrome], className),
18
23
  ...props,
19
24
  children: [chrome === "panel" ? /* @__PURE__ */ jsx(SkinAdornment, {
20
25
  scope: "chat-layout",
@@ -32,30 +37,38 @@ function ChatThread({ children, composer, className, ...props }) {
32
37
  scrollToBottom
33
38
  },
34
39
  children: /* @__PURE__ */ jsx("div", {
35
- ref: viewportRef,
36
40
  "data-control-ui": "chat-thread",
37
41
  "data-control-family": "chat-layout",
38
42
  "data-chat-layout-kind": "thread",
39
43
  "data-slot": "root",
40
44
  "data-at-bottom": atBottom ? "" : void 0,
41
- className: cn("min-h-0 min-w-0 flex-1 overflow-y-auto", className),
45
+ className: cn("flex min-h-0 min-w-0 flex-1 flex-col", className),
42
46
  ...props,
43
- children: /* @__PURE__ */ jsxs("div", {
44
- ref: contentRef,
45
- className: "relative flex min-h-full min-w-0 flex-col",
46
- children: [/* @__PURE__ */ jsx("div", {
47
- "data-control-ui": "chat-thread",
48
- "data-control-family": "chat-layout",
49
- "data-slot": "thread-content",
50
- className: "flex min-w-0 flex-1 flex-col",
51
- children
52
- }), composer ? /* @__PURE__ */ jsx("div", {
53
- "data-control-ui": "chat-thread",
54
- "data-control-family": "chat-layout",
55
- "data-slot": "dock",
56
- className: "sticky bottom-0 z-10 shrink-0",
57
- children: composer
58
- }) : null]
47
+ children: /* @__PURE__ */ jsx(ScrollArea, {
48
+ className: "flex min-h-0 flex-1 flex-col",
49
+ viewportClassName: "min-h-0 flex-1",
50
+ contentClassName: "flex min-h-full flex-col",
51
+ viewportRef,
52
+ lockAxis: "x",
53
+ mask: false,
54
+ blur: false,
55
+ children: /* @__PURE__ */ jsxs("div", {
56
+ ref: contentRef,
57
+ className: "relative flex min-w-0 flex-1 flex-col",
58
+ children: [/* @__PURE__ */ jsx("div", {
59
+ "data-control-ui": "chat-thread",
60
+ "data-control-family": "chat-layout",
61
+ "data-slot": "thread-content",
62
+ className: "flex min-w-0 flex-1 flex-col",
63
+ children
64
+ }), composer ? /* @__PURE__ */ jsx("div", {
65
+ "data-control-ui": "chat-thread",
66
+ "data-control-family": "chat-layout",
67
+ "data-slot": "dock",
68
+ className: "sticky bottom-0 z-10 shrink-0",
69
+ children: composer
70
+ }) : null]
71
+ })
59
72
  })
60
73
  })
61
74
  });