@ctrl-ui/react 0.8.19 → 0.8.20

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 (84) hide show
  1. package/README.md +7 -0
  2. package/dist/action-bar.js +2 -2
  3. package/dist/audio-device-select.js +5 -2
  4. package/dist/blocks/coding-agent.js +22 -12
  5. package/dist/blocks/design-canvas-inspector.js +6 -3
  6. package/dist/blocks/design-canvas.js +17 -11
  7. package/dist/blocks/discussion-markdown-input.d.ts +3 -1
  8. package/dist/blocks/discussion-markdown-input.js +3 -1
  9. package/dist/blocks/discussion.d.ts +3 -1
  10. package/dist/blocks/discussion.js +9 -4
  11. package/dist/blocks/file-explorer.js +47 -25
  12. package/dist/blocks/settings.js +8 -6
  13. package/dist/chat-composer-editor/extensions/mention.d.ts +1 -1
  14. package/dist/chat-message.d.ts +13 -0
  15. package/dist/chat-message.js +198 -3
  16. package/dist/filter-bar/editor.js +5 -2
  17. package/dist/knob-contracts/flow-knobs.d.ts +4 -0
  18. package/dist/knob-contracts/flow-knobs.js +32 -0
  19. package/dist/knob-contracts/item-knobs.d.ts +1 -1
  20. package/dist/knob-contracts/item-knobs.js +4 -1
  21. package/dist/knob-contracts/markdown-editor-knobs.d.ts +1 -1
  22. package/dist/knob-contracts/markdown-editor-knobs.js +1 -0
  23. package/dist/knob-contracts/morphing-panel-knobs.d.ts +1 -1
  24. package/dist/knob-contracts/morphing-panel-knobs.js +2 -1
  25. package/dist/knob-contracts/resize-handle-knobs.d.ts +4 -0
  26. package/dist/knob-contracts/resize-handle-knobs.js +12 -0
  27. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  28. package/dist/knob-contracts/sidebar-knobs.js +10 -0
  29. package/dist/lib/canvas-grid.d.ts +4 -0
  30. package/dist/lib/canvas-grid.js +5 -0
  31. package/dist/markdown-editor/drag-handle.d.ts +6 -0
  32. package/dist/markdown-editor/drag-handle.js +126 -0
  33. package/dist/markdown-editor/suggestions.d.ts +12 -0
  34. package/dist/markdown-editor/suggestions.js +235 -0
  35. package/dist/markdown-editor/toolbar.js +6 -3
  36. package/dist/markdown-editor.js +14 -4
  37. package/dist/styles/index.css +6 -3
  38. package/dist/styles/recipes/chat-message-edit.css +105 -0
  39. package/dist/styles/recipes/flow.css +304 -0
  40. package/dist/styles/recipes/infinite-canvas.css +1 -1
  41. package/dist/styles/recipes/item.css +39 -2
  42. package/dist/styles/recipes/markdown-editor.css +44 -6
  43. package/dist/styles/recipes/morphing-panel.css +53 -12
  44. package/dist/styles/recipes/page-layout.css +11 -1
  45. package/dist/styles/recipes/resize-handle.css +126 -0
  46. package/dist/styles/recipes/sidebar-mobile.css +171 -0
  47. package/dist/styles/recipes/toolbar.css +8 -1
  48. package/dist/styles/theme.css +1 -0
  49. package/dist/ui/app-shell.js +1 -1
  50. package/dist/ui/color-picker.js +1 -1
  51. package/dist/ui/combobox.d.ts +1 -1
  52. package/dist/ui/command.js +1 -1
  53. package/dist/ui/dockable-panel.js +1 -1
  54. package/dist/ui/flow.d.ts +51 -0
  55. package/dist/ui/flow.js +153 -0
  56. package/dist/ui/infinite-canvas.js +3 -3
  57. package/dist/ui/item.d.ts +5 -3
  58. package/dist/ui/item.js +2 -1
  59. package/dist/ui/morphing-panel.d.ts +21 -4
  60. package/dist/ui/morphing-panel.js +152 -36
  61. package/dist/ui/resize-handle.d.ts +11 -0
  62. package/dist/ui/resize-handle.js +26 -0
  63. package/dist/ui/responsive-dialog.js +1 -1
  64. package/dist/ui/sidebar-menu.js +1 -1
  65. package/dist/ui/sidebar-mobile.d.ts +17 -0
  66. package/dist/ui/sidebar-mobile.js +70 -0
  67. package/dist/ui/sidebar-provider.d.ts +1 -0
  68. package/dist/ui/sidebar-provider.js +3 -0
  69. package/dist/ui/sidebar.d.ts +3 -1
  70. package/dist/ui/sidebar.js +100 -48
  71. package/dist/ui/toolbar.d.ts +3 -1
  72. package/dist/ui/toolbar.js +2 -1
  73. package/dist/ui/typography.d.ts +5 -2
  74. package/dist/ui/typography.js +5 -3
  75. package/package.json +5 -1
  76. package/dist/environment-variables.d.ts +0 -125
  77. package/dist/environment-variables.js +0 -512
  78. package/dist/hooks/use-environment-variables.d.ts +0 -50
  79. package/dist/hooks/use-environment-variables.js +0 -188
  80. package/dist/knob-contracts/environment-variables-knobs.d.ts +0 -4
  81. package/dist/knob-contracts/environment-variables-knobs.js +0 -12
  82. package/dist/lib/env-file.d.ts +0 -27
  83. package/dist/lib/env-file.js +0 -218
  84. package/dist/styles/recipes/environment-variables.css +0 -170
@@ -1,9 +1,11 @@
1
1
  "use client";
2
2
  import { cn } from "./lib/cn.js";
3
+ import { LiveStatus } from "./ui/live-status.js";
4
+ import { ActionBar, ActionBarCopy, ActionBarEdit, ActionBarItem } from "./action-bar.js";
3
5
  import { ChatMessageStateProvider, chatAuthorLabels, chatMessageStatusLabels, chatMessageTransition, useChatMessage, useChatMessageContext, useChatThreadAnnounce } from "./hooks/use-chat-message.js";
4
6
  import { ChatMessageReaction, ChatMessageReactions, ChatMessageReplies, ChatMessageReplySummary, ChatTypingIndicator } from "./chat-message/social.js";
5
- import { useEffect, useRef } from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
9
  //#region src/chat-message.tsx
8
10
  function ChatMessage({ from, state = "idle", density = "comfortable", layout = "bubble", continuation = false, authorLabel = chatAuthorLabels[from], statusLabels, className, children, ...props }) {
9
11
  const message = useChatMessage({
@@ -137,6 +139,199 @@ function ChatMessageContent({ className, ...props }) {
137
139
  ...props
138
140
  });
139
141
  }
142
+ function useMessageEditor({ value, onSave, onSaveError }) {
143
+ const [editing, setEditing] = useState(false);
144
+ const [draft, setDraft] = useState(value);
145
+ const [originalValue, setOriginalValue] = useState(value);
146
+ const [saving, setSaving] = useState(false);
147
+ const [failed, setFailed] = useState(false);
148
+ const inputRef = useRef(null);
149
+ const triggerRef = useRef(null);
150
+ const savingRef = useRef(false);
151
+ const mountedRef = useRef(false);
152
+ const errorId = useId();
153
+ useEffect(() => {
154
+ mountedRef.current = true;
155
+ return () => {
156
+ mountedRef.current = false;
157
+ };
158
+ }, []);
159
+ useLayoutEffect(() => {
160
+ if (editing) {
161
+ const input = inputRef.current;
162
+ input?.focus({ preventScroll: true });
163
+ input?.setSelectionRange(input.value.length, input.value.length);
164
+ } else if (triggerRef.current) {
165
+ triggerRef.current.focus({ preventScroll: true });
166
+ triggerRef.current = null;
167
+ }
168
+ }, [editing]);
169
+ function startEditing() {
170
+ if (savingRef.current) return;
171
+ triggerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
172
+ setDraft(value);
173
+ setOriginalValue(value);
174
+ setFailed(false);
175
+ setEditing(true);
176
+ }
177
+ function cancelEditing() {
178
+ if (savingRef.current) return;
179
+ setFailed(false);
180
+ setEditing(false);
181
+ }
182
+ async function save() {
183
+ if (!editing || savingRef.current || !draft.trim()) return;
184
+ if (draft === value) {
185
+ cancelEditing();
186
+ return;
187
+ }
188
+ savingRef.current = true;
189
+ setSaving(true);
190
+ setFailed(false);
191
+ try {
192
+ await onSave(draft);
193
+ if (mountedRef.current) setEditing(false);
194
+ } catch (error) {
195
+ if (mountedRef.current) {
196
+ setFailed(true);
197
+ onSaveError?.(error);
198
+ }
199
+ } finally {
200
+ savingRef.current = false;
201
+ if (mountedRef.current) setSaving(false);
202
+ }
203
+ }
204
+ return {
205
+ editing,
206
+ draft,
207
+ setDraft,
208
+ saving,
209
+ failed,
210
+ inputRef,
211
+ errorId,
212
+ startEditing,
213
+ cancelEditing,
214
+ save,
215
+ displayValue: editing ? originalValue : value
216
+ };
217
+ }
218
+ /** Plain-text editing in the message surface. Children replace the default Copy/Edit actions. */
219
+ function ChatMessageEditable({ value, onSave, onSaveError, editorLabel = "Edit message", saveLabel = "Save", cancelLabel = "Cancel", savingLabel = "Saving…", saveErrorLabel = "Couldn't save your message. Try again.", children, className, ...props }) {
220
+ const message = useChatMessageContext();
221
+ const { editing, draft, setDraft, saving, failed, inputRef, errorId, startEditing, cancelEditing, save, displayValue } = useMessageEditor({
222
+ value,
223
+ onSave,
224
+ onSaveError
225
+ });
226
+ return /* @__PURE__ */ jsxs("div", {
227
+ ...props,
228
+ "data-control-ui": "chat-message",
229
+ "data-control-family": "chat-message",
230
+ "data-slot": "editable",
231
+ "data-editing": editing ? "" : void 0,
232
+ className: cn("min-w-0", className),
233
+ children: [
234
+ /* @__PURE__ */ jsxs(ChatMessageContent, {
235
+ "data-editable": "",
236
+ children: [
237
+ /* @__PURE__ */ jsx("span", {
238
+ "data-control-ui": "chat-message",
239
+ "data-control-family": "chat-message",
240
+ "data-slot": "edit-value",
241
+ "aria-hidden": editing,
242
+ children: displayValue || "​"
243
+ }),
244
+ /* @__PURE__ */ jsx("span", {
245
+ "data-control-ui": "chat-message",
246
+ "data-control-family": "chat-message",
247
+ "data-slot": "edit-sizer",
248
+ "aria-hidden": "true",
249
+ children: `${draft}\u200b`
250
+ }),
251
+ /* @__PURE__ */ jsx("textarea", {
252
+ ref: inputRef,
253
+ "data-control-ui": "chat-message",
254
+ "data-control-family": "chat-message",
255
+ "data-slot": "edit-input",
256
+ "aria-keyshortcuts": "Control+Enter Meta+Enter Escape",
257
+ "aria-label": editorLabel,
258
+ "aria-describedby": failed ? errorId : void 0,
259
+ "aria-hidden": !editing,
260
+ "aria-busy": saving,
261
+ tabIndex: editing ? 0 : -1,
262
+ readOnly: !editing || saving,
263
+ value: editing ? draft : value,
264
+ rows: 1,
265
+ onChange: (event) => setDraft(event.currentTarget.value),
266
+ onKeyDown: (event) => {
267
+ if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
268
+ if (event.key === "Escape") {
269
+ event.preventDefault();
270
+ event.stopPropagation();
271
+ cancelEditing();
272
+ } else if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
273
+ event.preventDefault();
274
+ save();
275
+ }
276
+ }
277
+ })
278
+ ]
279
+ }),
280
+ /* @__PURE__ */ jsxs("div", {
281
+ "data-control-ui": "chat-message",
282
+ "data-control-family": "chat-message",
283
+ "data-slot": "edit-toolbars",
284
+ children: [/* @__PURE__ */ jsx("div", {
285
+ "data-control-ui": "chat-message",
286
+ "data-control-family": "chat-message",
287
+ "data-slot": "edit-toolbar",
288
+ "data-active": !editing ? "" : void 0,
289
+ inert: editing,
290
+ "aria-hidden": editing,
291
+ children: /* @__PURE__ */ jsx(ActionBar, {
292
+ align: message.isEndAligned ? "end" : "start",
293
+ copyValue: value,
294
+ onEdit: startEditing,
295
+ children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ActionBarCopy, {}), /* @__PURE__ */ jsx(ActionBarEdit, {})] })
296
+ })
297
+ }), /* @__PURE__ */ jsx("div", {
298
+ "data-control-ui": "chat-message",
299
+ "data-control-family": "chat-message",
300
+ "data-slot": "edit-toolbar",
301
+ "data-active": editing ? "" : void 0,
302
+ inert: !editing,
303
+ "aria-hidden": !editing,
304
+ children: /* @__PURE__ */ jsxs(ActionBar, {
305
+ align: message.isEndAligned ? "end" : "start",
306
+ label: "Edit actions",
307
+ "aria-busy": saving,
308
+ children: [/* @__PURE__ */ jsx(ActionBarItem, {
309
+ type: "button",
310
+ disabled: saving || !draft.trim(),
311
+ onClick: () => void save(),
312
+ children: saveLabel
313
+ }), /* @__PURE__ */ jsx(ActionBarItem, {
314
+ type: "button",
315
+ disabled: saving,
316
+ onClick: cancelEditing,
317
+ children: cancelLabel
318
+ })]
319
+ })
320
+ })]
321
+ }),
322
+ /* @__PURE__ */ jsx(LiveStatus, { message: saving ? savingLabel : "" }),
323
+ /* @__PURE__ */ jsx("span", {
324
+ id: errorId,
325
+ role: "alert",
326
+ "data-control-ui": "chat-message",
327
+ "data-control-family": "chat-message",
328
+ "data-slot": "edit-error",
329
+ hidden: !failed,
330
+ children: failed ? saveErrorLabel : ""
331
+ })
332
+ ]
333
+ });
334
+ }
140
335
  /** Visual only: the pending transition is announced by `ChatMessage` through the thread's status region. */
141
336
  function ChatMessagePending({ className, ...props }) {
142
337
  if (!useChatMessageContext().isPending) return null;
@@ -190,4 +385,4 @@ function ChatMessageActions({ className, children, ...props }) {
190
385
  });
191
386
  }
192
387
  //#endregion
193
- export { ChatMessage, ChatMessageActions, ChatMessageAuthor, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageFooter, ChatMessageHeader, ChatMessagePending, ChatMessageReaction, ChatMessageReactions, ChatMessageReplies, ChatMessageReplySummary, ChatMessageRow, ChatMessageTime, ChatTypingIndicator };
388
+ export { ChatMessage, ChatMessageActions, ChatMessageAuthor, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageEditable, ChatMessageFooter, ChatMessageHeader, ChatMessagePending, ChatMessageReaction, ChatMessageReactions, ChatMessageReplies, ChatMessageReplySummary, ChatMessageRow, ChatMessageTime, ChatTypingIndicator };
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Button } from "../ui/button.js";
3
+ import { Text } from "../ui/typography.js";
3
4
  import { useFilterBarContext } from "./context.js";
4
5
  import { allowsCustomValue, fieldOperators, fieldOptions, parseCustomValue } from "./model.js";
5
6
  import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "../ui/combobox.js";
@@ -75,9 +76,11 @@ function handleEditorKeyDown(context, draft, event) {
75
76
  return true;
76
77
  }
77
78
  function EditorOptions({ field, invalidNumber, owner }) {
78
- if (field?.loading || field?.error) return /* @__PURE__ */ jsx("div", {
79
+ if (field?.loading || field?.error) return /* @__PURE__ */ jsx(Text, {
80
+ as: "div",
81
+ size: "caption",
79
82
  id: statusId(owner),
80
- className: "p-3 text-caption",
83
+ className: "p-3",
81
84
  children: field.error ?? "Loading values…"
82
85
  });
83
86
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: invalidNumber ? "Enter a number, like 42 or 3.5." : "No matching options." }), /* @__PURE__ */ jsx(ComboboxList, { children: (choice) => /* @__PURE__ */ jsx(ComboboxItem, {
@@ -0,0 +1,4 @@
1
+ //#region src/knob-contracts/flow-knobs.d.ts
2
+ export declare const flowKnobs: readonly ["--cui-flow-background", "--cui-flow-grid-dot-color", "--cui-flow-edge-stroke", "--cui-flow-selected-edge-stroke", "--cui-flow-edge-size", "--cui-flow-node-background", "--cui-flow-node-foreground", "--cui-flow-node-border-color", "--cui-flow-node-hover-border-color", "--cui-flow-node-selected-border-color", "--cui-flow-node-border-width", "--cui-flow-node-radius", "--cui-flow-node-shadow", "--cui-flow-handle-size", "--cui-flow-handle-background", "--cui-flow-handle-border-color", "--cui-flow-handle-border-width", "--cui-flow-edge-label-background", "--cui-flow-edge-label-foreground", "--cui-flow-edge-label-border-color", "--cui-flow-edge-label-border-width", "--cui-flow-controls-item-radius", "--cui-flow-controls-padding", "--cui-flow-controls-radius", "--cui-flow-controls-background", "--cui-flow-controls-border-color", "--cui-flow-controls-shadow"];
3
+ export type FlowKnobStyle = Partial<Record<(typeof flowKnobs)[number], string>>;
4
+ //#endregion
@@ -0,0 +1,32 @@
1
+ //#region src/knob-contracts/flow-knobs.ts
2
+ const flowKnobs = [
3
+ "--cui-flow-background",
4
+ "--cui-flow-grid-dot-color",
5
+ "--cui-flow-edge-stroke",
6
+ "--cui-flow-selected-edge-stroke",
7
+ "--cui-flow-edge-size",
8
+ "--cui-flow-node-background",
9
+ "--cui-flow-node-foreground",
10
+ "--cui-flow-node-border-color",
11
+ "--cui-flow-node-hover-border-color",
12
+ "--cui-flow-node-selected-border-color",
13
+ "--cui-flow-node-border-width",
14
+ "--cui-flow-node-radius",
15
+ "--cui-flow-node-shadow",
16
+ "--cui-flow-handle-size",
17
+ "--cui-flow-handle-background",
18
+ "--cui-flow-handle-border-color",
19
+ "--cui-flow-handle-border-width",
20
+ "--cui-flow-edge-label-background",
21
+ "--cui-flow-edge-label-foreground",
22
+ "--cui-flow-edge-label-border-color",
23
+ "--cui-flow-edge-label-border-width",
24
+ "--cui-flow-controls-item-radius",
25
+ "--cui-flow-controls-padding",
26
+ "--cui-flow-controls-radius",
27
+ "--cui-flow-controls-background",
28
+ "--cui-flow-controls-border-color",
29
+ "--cui-flow-controls-shadow"
30
+ ];
31
+ //#endregion
32
+ export { flowKnobs };
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/item-knobs.d.ts
2
- export declare const itemKnobs: readonly ["--cui-item-radius", "--cui-item-hover-background", "--cui-item-hover-border-color", "--cui-item-border-width", "--cui-item-active-scale"];
2
+ export declare const itemKnobs: readonly ["--cui-item-radius", "--cui-item-hover-background", "--cui-item-hover-border-color", "--cui-item-border-width", "--cui-item-active-scale", "--cui-item-gap", "--cui-item-padding-block", "--cui-item-padding-inline"];
3
3
  export type ItemKnobStyle = Partial<Record<(typeof itemKnobs)[number], string>>;
4
4
  //#endregion
@@ -4,7 +4,10 @@ const itemKnobs = [
4
4
  "--cui-item-hover-background",
5
5
  "--cui-item-hover-border-color",
6
6
  "--cui-item-border-width",
7
- "--cui-item-active-scale"
7
+ "--cui-item-active-scale",
8
+ "--cui-item-gap",
9
+ "--cui-item-padding-block",
10
+ "--cui-item-padding-inline"
8
11
  ];
9
12
  //#endregion
10
13
  export { itemKnobs };
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/markdown-editor-knobs.d.ts
2
- export declare const markdownEditorKnobs: readonly ["--cui-markdown-editor-background", "--cui-markdown-editor-foreground", "--cui-markdown-editor-border-color", "--cui-markdown-editor-radius", "--cui-markdown-editor-padding", "--cui-markdown-editor-min-height", "--cui-markdown-editor-max-height", "--cui-markdown-editor-gap", "--cui-markdown-editor-muted-foreground", "--cui-markdown-editor-code-background", "--cui-markdown-editor-border-width"];
2
+ export declare const markdownEditorKnobs: readonly ["--cui-markdown-editor-background", "--cui-markdown-editor-foreground", "--cui-markdown-editor-border-color", "--cui-markdown-editor-radius", "--cui-markdown-editor-padding", "--cui-markdown-editor-gutter-size", "--cui-markdown-editor-min-height", "--cui-markdown-editor-max-height", "--cui-markdown-editor-gap", "--cui-markdown-editor-muted-foreground", "--cui-markdown-editor-code-background", "--cui-markdown-editor-border-width"];
3
3
  export type MarkdownEditorKnobStyle = Partial<Record<(typeof markdownEditorKnobs)[number], string>>;
4
4
  //#endregion
@@ -5,6 +5,7 @@ const markdownEditorKnobs = [
5
5
  "--cui-markdown-editor-border-color",
6
6
  "--cui-markdown-editor-radius",
7
7
  "--cui-markdown-editor-padding",
8
+ "--cui-markdown-editor-gutter-size",
8
9
  "--cui-markdown-editor-min-height",
9
10
  "--cui-markdown-editor-max-height",
10
11
  "--cui-markdown-editor-gap",
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/morphing-panel-knobs.d.ts
2
- export declare const morphingPanelKnobs: readonly ["--cui-morphing-panel-radius", "--cui-morphing-panel-background", "--cui-morphing-panel-foreground", "--cui-morphing-panel-border-color", "--cui-morphing-panel-shadow", "--cui-morphing-panel-trigger-hover-background"];
2
+ export declare const morphingPanelKnobs: readonly ["--cui-morphing-panel-radius", "--cui-morphing-panel-background", "--cui-morphing-panel-foreground", "--cui-morphing-panel-border-color", "--cui-morphing-panel-shadow", "--cui-morphing-panel-trigger-hover-background", "--cui-morphing-panel-border-width"];
3
3
  export type MorphingPanelKnobStyle = Partial<Record<(typeof morphingPanelKnobs)[number], string>>;
4
4
  //#endregion
@@ -5,7 +5,8 @@ const morphingPanelKnobs = [
5
5
  "--cui-morphing-panel-foreground",
6
6
  "--cui-morphing-panel-border-color",
7
7
  "--cui-morphing-panel-shadow",
8
- "--cui-morphing-panel-trigger-hover-background"
8
+ "--cui-morphing-panel-trigger-hover-background",
9
+ "--cui-morphing-panel-border-width"
9
10
  ];
10
11
  //#endregion
11
12
  export { morphingPanelKnobs };
@@ -0,0 +1,4 @@
1
+ //#region src/knob-contracts/resize-handle-knobs.d.ts
2
+ export declare const resizeHandleKnobs: readonly ["--cui-resize-handle-size", "--cui-resize-handle-hit-size", "--cui-resize-handle-background", "--cui-resize-handle-hover-background", "--cui-resize-handle-border-color", "--cui-resize-handle-border-width", "--cui-resize-handle-radius"];
3
+ export type ResizeHandleKnobStyle = Partial<Record<(typeof resizeHandleKnobs)[number], string>>;
4
+ //#endregion
@@ -0,0 +1,12 @@
1
+ //#region src/knob-contracts/resize-handle-knobs.ts
2
+ const resizeHandleKnobs = [
3
+ "--cui-resize-handle-size",
4
+ "--cui-resize-handle-hit-size",
5
+ "--cui-resize-handle-background",
6
+ "--cui-resize-handle-hover-background",
7
+ "--cui-resize-handle-border-color",
8
+ "--cui-resize-handle-border-width",
9
+ "--cui-resize-handle-radius"
10
+ ];
11
+ //#endregion
12
+ export { resizeHandleKnobs };
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/sidebar-knobs.d.ts
2
- export declare const sidebarKnobs: readonly ["--cui-sidebar-menu-badge-height", "--cui-sidebar-menu-badge-background", "--cui-sidebar-menu-badge-foreground", "--cui-sidebar-rail-divider-background", "--cui-sidebar-wrapper-background", "--cui-sidebar-wrapper-background-image", "--cui-sidebar-inner-background", "--cui-sidebar-inner-border-color", "--cui-sidebar-inner-background-image", "--cui-sidebar-inner-backdrop-filter", "--cui-sidebar-inner-border-width", "--cui-sidebar-inner-radius", "--cui-sidebar-inner-shadow", "--cui-sidebar-header-background", "--cui-sidebar-header-shadow", "--cui-sidebar-inset-background", "--cui-sidebar-inset-background-image", "--cui-sidebar-inset-shadow", "--cui-sidebar-inset-border-color", "--cui-sidebar-inset-border-width", "--cui-sidebar-inset-radius", "--cui-sidebar-inset-inset", "--cui-sidebar-menu-button-radius", "--cui-sidebar-group-label-foreground", "--cui-sidebar-menu-button-foreground", "--cui-sidebar-menu-button-hover-background", "--cui-sidebar-menu-button-hover-foreground", "--cui-sidebar-menu-button-hover-shadow", "--cui-sidebar-menu-button-active-background", "--cui-sidebar-menu-button-active-foreground", "--cui-sidebar-menu-button-active-shadow", "--cui-sidebar-menu-button-track-active-foreground", "--cui-sidebar-menu-button-height", "--cui-sidebar-menu-button-padding-block", "--cui-sidebar-menu-button-padding-inline", "--cui-sidebar-menu-button-font-size", "--cui-sidebar-menu-button-font-weight", "--cui-sidebar-menu-button-active-font-weight", "--cui-sidebar-group-label-font-size", "--cui-sidebar-group-label-font-weight", "--cui-sidebar-group-label-letter-spacing", "--cui-sidebar-group-label-text-transform", "--cui-sidebar-group-label-padding", "--cui-sidebar-skip-link-background", "--cui-sidebar-skip-link-foreground", "--cui-sidebar-skip-link-radius", "--cui-sidebar-skip-link-shadow"];
2
+ export declare const sidebarKnobs: readonly ["--cui-sidebar-nav-height", "--cui-sidebar-nav-background", "--cui-sidebar-nav-foreground", "--cui-sidebar-nav-border-color", "--cui-sidebar-nav-border-width", "--cui-sidebar-nav-shadow", "--cui-sidebar-nav-item-radius", "--cui-sidebar-nav-item-active-background", "--cui-sidebar-nav-item-active-foreground", "--cui-sidebar-nav-gap", "--cui-sidebar-menu-badge-height", "--cui-sidebar-menu-badge-background", "--cui-sidebar-menu-badge-foreground", "--cui-sidebar-rail-divider-background", "--cui-sidebar-wrapper-background", "--cui-sidebar-wrapper-background-image", "--cui-sidebar-inner-background", "--cui-sidebar-inner-border-color", "--cui-sidebar-inner-background-image", "--cui-sidebar-inner-backdrop-filter", "--cui-sidebar-inner-border-width", "--cui-sidebar-inner-radius", "--cui-sidebar-inner-shadow", "--cui-sidebar-header-background", "--cui-sidebar-header-shadow", "--cui-sidebar-inset-background", "--cui-sidebar-inset-background-image", "--cui-sidebar-inset-shadow", "--cui-sidebar-inset-border-color", "--cui-sidebar-inset-border-width", "--cui-sidebar-inset-radius", "--cui-sidebar-inset-inset", "--cui-sidebar-menu-button-radius", "--cui-sidebar-group-label-foreground", "--cui-sidebar-menu-button-foreground", "--cui-sidebar-menu-button-hover-background", "--cui-sidebar-menu-button-hover-foreground", "--cui-sidebar-menu-button-hover-shadow", "--cui-sidebar-menu-button-active-background", "--cui-sidebar-menu-button-active-foreground", "--cui-sidebar-menu-button-active-shadow", "--cui-sidebar-menu-button-track-active-foreground", "--cui-sidebar-menu-button-height", "--cui-sidebar-menu-button-padding-block", "--cui-sidebar-menu-button-padding-inline", "--cui-sidebar-menu-button-font-size", "--cui-sidebar-menu-button-font-weight", "--cui-sidebar-menu-button-active-font-weight", "--cui-sidebar-group-label-font-size", "--cui-sidebar-group-label-font-weight", "--cui-sidebar-group-label-letter-spacing", "--cui-sidebar-group-label-text-transform", "--cui-sidebar-group-label-padding", "--cui-sidebar-skip-link-background", "--cui-sidebar-skip-link-foreground", "--cui-sidebar-skip-link-radius", "--cui-sidebar-skip-link-shadow"];
3
3
  export type SidebarKnobStyle = Partial<Record<(typeof sidebarKnobs)[number], string>>;
4
4
  //#endregion
@@ -1,5 +1,15 @@
1
1
  //#region src/knob-contracts/sidebar-knobs.ts
2
2
  const sidebarKnobs = [
3
+ "--cui-sidebar-nav-height",
4
+ "--cui-sidebar-nav-background",
5
+ "--cui-sidebar-nav-foreground",
6
+ "--cui-sidebar-nav-border-color",
7
+ "--cui-sidebar-nav-border-width",
8
+ "--cui-sidebar-nav-shadow",
9
+ "--cui-sidebar-nav-item-radius",
10
+ "--cui-sidebar-nav-item-active-background",
11
+ "--cui-sidebar-nav-item-active-foreground",
12
+ "--cui-sidebar-nav-gap",
3
13
  "--cui-sidebar-menu-badge-height",
4
14
  "--cui-sidebar-menu-badge-background",
5
15
  "--cui-sidebar-menu-badge-foreground",
@@ -0,0 +1,4 @@
1
+ //#region src/lib/canvas-grid.d.ts
2
+ export declare const CANVAS_GRID_GAP = 24;
3
+ export declare const CANVAS_GRID_DOT_RADIUS = 1;
4
+ //#endregion
@@ -0,0 +1,5 @@
1
+ //#region src/lib/canvas-grid.ts
2
+ const CANVAS_GRID_GAP = 24;
3
+ const CANVAS_GRID_DOT_RADIUS = 1;
4
+ //#endregion
5
+ export { CANVAS_GRID_DOT_RADIUS, CANVAS_GRID_GAP };
@@ -0,0 +1,6 @@
1
+ import { RefObject } from "react";
2
+ //#region src/markdown-editor/drag-handle.d.ts
3
+ export declare function MarkdownEditorDragHandle({ container }: {
4
+ container: RefObject<HTMLDivElement | null>;
5
+ }): import("react").JSX.Element | null;
6
+ //#endregion
@@ -0,0 +1,126 @@
1
+ "use client";
2
+ import { Button } from "../ui/button.js";
3
+ import { LiveStatus } from "../ui/live-status.js";
4
+ import { useMarkdownEditor } from "./context.js";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
+ import { GripVerticalIcon } from "lucide-react";
8
+ import { NodeSelection } from "@tiptap/pm/state";
9
+ import { closeHistory } from "@tiptap/pm/history";
10
+ //#region src/markdown-editor/drag-handle.tsx
11
+ function MarkdownEditorDragHandle({ container }) {
12
+ const { editor, disabled, source } = useMarkdownEditor();
13
+ const [block, setBlock] = useState(null);
14
+ const [announcement, setAnnouncement] = useState("");
15
+ const button = useRef(null);
16
+ const dragging = useRef(false);
17
+ useEffect(() => {
18
+ const surface = container.current;
19
+ if (!editor || !surface || disabled || source) return;
20
+ function show(blockPosition) {
21
+ if (!editor || !surface) return;
22
+ const node = editor.view.nodeDOM(blockPosition);
23
+ if (!(node instanceof HTMLElement)) return;
24
+ const top = node.getBoundingClientRect().top - surface.getBoundingClientRect().top + surface.scrollTop;
25
+ setBlock((current) => current?.position === blockPosition && current.top === top ? current : {
26
+ position: blockPosition,
27
+ top
28
+ });
29
+ }
30
+ function track(event) {
31
+ if (!editor || dragging.current || button.current?.contains(event.target instanceof Node ? event.target : null)) return;
32
+ const rect = editor.view.dom.getBoundingClientRect();
33
+ const padding = Number.parseFloat(getComputedStyle(editor.view.dom).paddingInlineStart);
34
+ const hit = editor.view.posAtCoords({
35
+ left: Math.max(event.clientX, rect.left + padding),
36
+ top: event.clientY
37
+ });
38
+ if (!hit) return;
39
+ const resolved = editor.state.doc.resolve(hit.pos);
40
+ show(resolved.depth > 0 ? resolved.before(1) : hit.pos);
41
+ }
42
+ function selectionChanged() {
43
+ if (!editor || dragging.current) return;
44
+ const { $from } = editor.state.selection;
45
+ show($from.depth > 0 ? $from.before(1) : $from.pos);
46
+ }
47
+ function hide() {
48
+ if (!dragging.current && document.activeElement !== button.current) setBlock(null);
49
+ }
50
+ surface.addEventListener("mousemove", track);
51
+ surface.addEventListener("mouseleave", hide);
52
+ editor.on("transaction", selectionChanged).on("focus", selectionChanged);
53
+ return () => {
54
+ surface.removeEventListener("mousemove", track);
55
+ surface.removeEventListener("mouseleave", hide);
56
+ editor.off("transaction", selectionChanged).off("focus", selectionChanged);
57
+ editor.view.dragging = null;
58
+ };
59
+ }, [
60
+ editor,
61
+ container,
62
+ disabled,
63
+ source
64
+ ]);
65
+ if (!editor || disabled || source) return null;
66
+ const selected = editor.state.selection.$from;
67
+ const position = block?.position ?? (selected.depth > 0 ? selected.before(1) : selected.pos);
68
+ function move(direction) {
69
+ if (!editor) return;
70
+ const { doc, tr } = editor.state;
71
+ const node = doc.nodeAt(position);
72
+ if (!node) return;
73
+ const adjacent = direction === -1 ? doc.resolve(position).nodeBefore : doc.nodeAt(position + node.nodeSize);
74
+ if (!adjacent) return;
75
+ const target = direction === -1 ? position - adjacent.nodeSize : position + adjacent.nodeSize;
76
+ tr.delete(position, position + node.nodeSize).insert(target, node);
77
+ tr.setSelection(NodeSelection.create(tr.doc, target));
78
+ editor.view.dispatch(closeHistory(tr).scrollIntoView());
79
+ setAnnouncement(`Block moved ${direction === -1 ? "up" : "down"}.`);
80
+ }
81
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
82
+ ref: button,
83
+ type: "button",
84
+ variant: "ghost",
85
+ size: "xs",
86
+ iconOnly: true,
87
+ "data-markdown-drag-handle": "",
88
+ "data-visible": block ? "" : void 0,
89
+ "aria-label": "Drag to reorder block",
90
+ "aria-description": "Drag this block, or use Alt+ArrowUp and Alt+ArrowDown to move it.",
91
+ title: "Drag to reorder · Alt+↑/↓ to move",
92
+ draggable: true,
93
+ style: { top: block?.top },
94
+ onClick: () => editor.commands.setNodeSelection(position),
95
+ onKeyDown: (event) => {
96
+ if (!event.altKey || !["ArrowUp", "ArrowDown"].includes(event.key)) return;
97
+ event.preventDefault();
98
+ move(event.key === "ArrowUp" ? -1 : 1);
99
+ },
100
+ onDragStart: (event) => {
101
+ const { view } = editor;
102
+ const selection = NodeSelection.create(view.state.doc, position);
103
+ const node = view.nodeDOM(position);
104
+ view.dispatch(view.state.tr.setSelection(selection));
105
+ const { dom, text, slice } = view.serializeForClipboard(selection.content());
106
+ event.dataTransfer.clearData();
107
+ event.dataTransfer.setData("text/html", dom.innerHTML);
108
+ event.dataTransfer.setData("text/plain", text);
109
+ event.dataTransfer.effectAllowed = "copyMove";
110
+ if (node instanceof HTMLElement) event.dataTransfer.setDragImage(node, 0, 0);
111
+ view.dragging = {
112
+ slice,
113
+ move: true
114
+ };
115
+ dragging.current = true;
116
+ },
117
+ onDragEnd: () => {
118
+ editor.view.dragging = null;
119
+ dragging.current = false;
120
+ setBlock(null);
121
+ },
122
+ children: /* @__PURE__ */ jsx(GripVerticalIcon, { "aria-hidden": "true" })
123
+ }), /* @__PURE__ */ jsx(LiveStatus, { message: announcement })] });
124
+ }
125
+ //#endregion
126
+ export { MarkdownEditorDragHandle };
@@ -0,0 +1,12 @@
1
+ import { TriggerMenuItemData } from "../hooks/use-trigger-menu.js";
2
+ //#region src/markdown-editor/suggestions.d.ts
3
+ export type MarkdownEditorMention = TriggerMenuItemData & {
4
+ href: string;
5
+ };
6
+ export type MarkdownEditorSuggestionsProps = {
7
+ /** The application supplies people and their persistent profile URLs. */
8
+ mentions?: readonly MarkdownEditorMention[] | ((query: string) => readonly MarkdownEditorMention[]);
9
+ onMentionSelect?: (mention: MarkdownEditorMention) => void;
10
+ };
11
+ export declare function MarkdownEditorSuggestions({ mentions, onMentionSelect }: MarkdownEditorSuggestionsProps): import("react").JSX.Element;
12
+ //#endregion