@ctrl-ui/react 0.8.6 → 0.8.8
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.
- package/dist/action-bar.d.ts +15 -7
- package/dist/action-bar.js +39 -19
- package/dist/activity.js +7 -15
- package/dist/audio-recorder.d.ts +14 -4
- package/dist/audio-recorder.js +82 -29
- package/dist/audio-visualizer.js +1 -1
- package/dist/blocks/coding-agent.js +54 -57
- package/dist/blocks/design-canvas-data.d.ts +32 -0
- package/dist/blocks/design-canvas-data.js +100 -0
- package/dist/blocks/design-canvas-inspector.d.ts +8 -0
- package/dist/blocks/design-canvas-inspector.js +238 -0
- package/dist/blocks/design-canvas.d.ts +21 -0
- package/dist/blocks/design-canvas.js +452 -0
- package/dist/blocks/discussion.d.ts +28 -0
- package/dist/blocks/discussion.js +67 -0
- package/dist/blocks/file-explorer-data.d.ts +1 -0
- package/dist/blocks/file-explorer-data.js +4 -1
- package/dist/blocks/file-explorer.d.ts +9 -1
- package/dist/blocks/file-explorer.js +461 -192
- package/dist/blocks/settings.d.ts +2 -1
- package/dist/blocks/settings.js +101 -79
- package/dist/blocks/team-chat.d.ts +12 -0
- package/dist/blocks/team-chat.js +26 -0
- package/dist/blocks/theme-toggle.js +23 -31
- package/dist/chat-composer-attachment.d.ts +12 -3
- package/dist/chat-composer-attachment.js +61 -19
- package/dist/chat-composer-editor/extensions/mention.d.ts +5 -1
- package/dist/chat-composer-editor/extensions/mention.js +58 -27
- package/dist/chat-composer-editor/ghost.js +2 -4
- package/dist/chat-composer-editor/types.d.ts +5 -0
- package/dist/chat-composer-editor.d.ts +1 -1
- package/dist/chat-composer-editor.js +71 -28
- package/dist/chat-composer.d.ts +13 -32
- package/dist/chat-composer.js +54 -13
- package/dist/chat-layout.d.ts +19 -0
- package/dist/chat-layout.js +96 -32
- package/dist/chat-message/social.d.ts +26 -0
- package/dist/chat-message/social.js +122 -0
- package/dist/chat-message.d.ts +28 -29
- package/dist/chat-message.js +106 -25
- package/dist/context.d.ts +2 -2
- package/dist/context.js +38 -23
- package/dist/dynamic-notification-canvas-loop.d.ts +24 -0
- package/dist/dynamic-notification-canvas-loop.js +113 -0
- package/dist/dynamic-notification-glass.js +23 -114
- package/dist/dynamic-notification-liquid.js +14 -118
- package/dist/dynamic-notification.d.ts +16 -8
- package/dist/dynamic-notification.js +85 -22
- package/dist/email/email-brand.d.ts +7 -4
- package/dist/email/email-brand.js +9 -6
- package/dist/email/email-code.d.ts +3 -2
- package/dist/email/email-code.js +11 -9
- package/dist/email/email-footer.d.ts +5 -3
- package/dist/email/email-footer.js +11 -5
- package/dist/email/email.d.ts +31 -9
- package/dist/email/email.js +50 -26
- package/dist/email/templates.d.ts +10 -9
- package/dist/email/templates.js +102 -38
- package/dist/email/theme.js +1 -1
- package/dist/environment-variables.d.ts +4 -2
- package/dist/environment-variables.js +52 -32
- package/dist/filter-bar/context.d.ts +32 -0
- package/dist/filter-bar/context.js +11 -0
- package/dist/filter-bar/editor.d.ts +14 -0
- package/dist/filter-bar/editor.js +216 -0
- package/dist/filter-bar/model.d.ts +10 -0
- package/dist/filter-bar/model.js +57 -0
- package/dist/filter-bar/motion.d.ts +10 -0
- package/dist/filter-bar/motion.js +84 -0
- package/dist/filter-bar/parts.d.ts +19 -0
- package/dist/filter-bar/parts.js +244 -0
- package/dist/filter-bar/types.d.ts +54 -0
- package/dist/filter-bar/types.js +0 -0
- package/dist/filter-bar.d.ts +27 -0
- package/dist/filter-bar.js +310 -0
- package/dist/hooks/use-audio-recorder.js +12 -6
- package/dist/hooks/use-chat-composer.d.ts +21 -7
- package/dist/hooks/use-chat-composer.js +32 -5
- package/dist/hooks/use-chat-message.d.ts +28 -2
- package/dist/hooks/use-chat-message.js +41 -10
- package/dist/hooks/use-chat-thread-scroll.js +43 -7
- package/dist/hooks/use-color-area.d.ts +3 -3
- package/dist/hooks/use-color-area.js +13 -12
- package/dist/hooks/use-copy-to-clipboard.d.ts +2 -1
- package/dist/hooks/use-copy-to-clipboard.js +20 -20
- package/dist/hooks/use-dropzone.d.ts +7 -4
- package/dist/hooks/use-dropzone.js +34 -3
- package/dist/hooks/use-dynamic-notification.d.ts +6 -2
- package/dist/hooks/use-dynamic-notification.js +26 -10
- package/dist/hooks/use-mobile.d.ts +3 -2
- package/dist/hooks/use-mobile.js +8 -4
- package/dist/hooks/use-textarea-trigger-menu.js +2 -1
- package/dist/hooks/use-trigger-menu.d.ts +54 -0
- package/dist/hooks/use-trigger-menu.js +102 -47
- package/dist/hooks/use-user-ask.d.ts +2 -0
- package/dist/hooks/use-user-ask.js +35 -41
- package/dist/inline-attachment.d.ts +6 -2
- package/dist/inline-attachment.js +44 -29
- package/dist/inline-citation.d.ts +12 -5
- package/dist/inline-citation.js +25 -16
- package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
- package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
- package/dist/knob-contracts/audio-visualizer-knobs.d.ts +1 -1
- package/dist/knob-contracts/audio-visualizer-knobs.js +2 -1
- package/dist/knob-contracts/avatar-knobs.d.ts +1 -1
- package/dist/knob-contracts/avatar-knobs.js +6 -1
- package/dist/knob-contracts/badge-knobs.d.ts +1 -1
- package/dist/knob-contracts/badge-knobs.js +2 -1
- package/dist/knob-contracts/breadcrumb-knobs.d.ts +4 -0
- package/dist/knob-contracts/breadcrumb-knobs.js +8 -0
- package/dist/knob-contracts/button-knobs.d.ts +1 -1
- package/dist/knob-contracts/button-knobs.js +1 -0
- package/dist/knob-contracts/chat-layout-knobs.d.ts +1 -1
- package/dist/knob-contracts/chat-layout-knobs.js +2 -1
- package/dist/knob-contracts/chat-message-knobs.d.ts +1 -1
- package/dist/knob-contracts/chat-message-knobs.js +8 -1
- package/dist/knob-contracts/choice-knobs.d.ts +1 -1
- package/dist/knob-contracts/choice-knobs.js +1 -0
- package/dist/knob-contracts/code-diff-knobs.d.ts +1 -1
- package/dist/knob-contracts/code-diff-knobs.js +2 -0
- package/dist/knob-contracts/code-knobs.d.ts +1 -1
- package/dist/knob-contracts/code-knobs.js +2 -1
- package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
- package/dist/knob-contracts/collapsible-knobs.js +1 -1
- package/dist/knob-contracts/dynamic-notification-knobs.d.ts +1 -1
- package/dist/knob-contracts/dynamic-notification-knobs.js +2 -0
- package/dist/knob-contracts/emoji-picker-knobs.d.ts +4 -0
- package/dist/knob-contracts/emoji-picker-knobs.js +10 -0
- package/dist/knob-contracts/field-knobs.d.ts +1 -1
- package/dist/knob-contracts/field-knobs.js +5 -1
- package/dist/knob-contracts/filter-bar-knobs.d.ts +4 -0
- package/dist/knob-contracts/filter-bar-knobs.js +15 -0
- package/dist/knob-contracts/infinite-canvas-knobs.d.ts +1 -1
- package/dist/knob-contracts/infinite-canvas-knobs.js +3 -0
- package/dist/knob-contracts/markdown-knobs.d.ts +1 -1
- package/dist/knob-contracts/markdown-knobs.js +3 -1
- package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
- package/dist/knob-contracts/page-layout-knobs.js +4 -1
- package/dist/knob-contracts/popup-knobs.d.ts +1 -1
- package/dist/knob-contracts/popup-knobs.js +28 -0
- package/dist/knob-contracts/range-knobs.d.ts +1 -1
- package/dist/knob-contracts/range-knobs.js +1 -0
- package/dist/knob-contracts/rich-tooltip-knobs.d.ts +1 -1
- package/dist/knob-contracts/rich-tooltip-knobs.js +2 -1
- package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
- package/dist/knob-contracts/sidebar-knobs.js +27 -2
- package/dist/knob-contracts/spinner-knobs.d.ts +1 -1
- package/dist/knob-contracts/spinner-knobs.js +1 -1
- package/dist/knob-contracts/stepper-knobs.d.ts +1 -1
- package/dist/knob-contracts/stepper-knobs.js +4 -1
- package/dist/knob-contracts/switch-knobs.d.ts +1 -1
- package/dist/knob-contracts/switch-knobs.js +5 -0
- package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
- package/dist/knob-contracts/table-of-contents-knobs.js +4 -5
- package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
- package/dist/knob-contracts/tabs-knobs.js +1 -0
- package/dist/knob-contracts/tree-knobs.d.ts +1 -1
- package/dist/knob-contracts/tree-knobs.js +2 -1
- package/dist/lib/cn.js +0 -4
- package/dist/lib/contrast.d.ts +1 -0
- package/dist/lib/contrast.js +17 -5
- package/dist/lib/dropzone-validation.js +41 -28
- package/dist/lib/env-file.d.ts +1 -1
- package/dist/lib/env-file.js +5 -5
- package/dist/lib/motion.d.ts +4 -0
- package/dist/lib/motion.js +15 -0
- package/dist/lib/trigger-detect.js +6 -8
- package/dist/markdown-block.js +13 -8
- package/dist/popup-parts.d.ts +8 -0
- package/dist/popup-parts.js +43 -0
- package/dist/scripts/contrast-eval.js +5 -4
- package/dist/source-badge.js +3 -1
- package/dist/styles/code.css +0 -2
- package/dist/styles/effects.css +6 -32
- package/dist/styles/index.css +14 -8
- package/dist/styles/recipes/accordion.css +9 -6
- package/dist/styles/recipes/action-bar.css +0 -15
- package/dist/styles/recipes/activity.css +5 -11
- package/dist/styles/recipes/alert.css +4 -4
- package/dist/styles/recipes/audio-recorder.css +15 -3
- package/dist/styles/recipes/audio-visualizer.css +13 -2
- package/dist/styles/recipes/avatar.css +78 -1
- package/dist/styles/recipes/badge.css +14 -4
- package/dist/styles/recipes/breadcrumb.css +77 -0
- package/dist/styles/recipes/button-group.css +6 -8
- package/dist/styles/recipes/button-states.css +66 -0
- package/dist/styles/recipes/button.css +26 -85
- package/dist/styles/recipes/calendar.css +17 -23
- package/dist/styles/recipes/card.css +2 -2
- package/dist/styles/recipes/chat-composer-attachment.css +9 -3
- package/dist/styles/recipes/chat-composer.css +21 -16
- package/dist/styles/recipes/chat-layout.css +64 -2
- package/dist/styles/recipes/chat-message-social.css +123 -0
- package/dist/styles/recipes/chat-message.css +157 -37
- package/dist/styles/recipes/choice.css +25 -21
- package/dist/styles/recipes/code-diff.css +51 -26
- package/dist/styles/recipes/code.css +44 -18
- package/dist/styles/recipes/collapsible.css +43 -1
- package/dist/styles/recipes/color-picker-details.css +3 -4
- package/dist/styles/recipes/color-picker-values.css +9 -8
- package/dist/styles/recipes/color-picker.css +23 -4
- package/dist/styles/recipes/context.css +3 -3
- package/dist/styles/recipes/dockable-panel.css +18 -7
- package/dist/styles/recipes/dropzone.css +7 -8
- package/dist/styles/recipes/dynamic-notification-input.css +8 -5
- package/dist/styles/recipes/dynamic-notification-motion.css +12 -21
- package/dist/styles/recipes/dynamic-notification.css +40 -26
- package/dist/styles/recipes/emoji-picker.css +132 -0
- package/dist/styles/recipes/empty.css +5 -4
- package/dist/styles/recipes/environment-variables.css +4 -15
- package/dist/styles/recipes/field-parts.css +89 -21
- package/dist/styles/recipes/field.css +83 -26
- package/dist/styles/recipes/filter-bar.css +161 -0
- package/dist/styles/recipes/gradient-editor.css +18 -7
- package/dist/styles/recipes/infinite-canvas.css +51 -4
- package/dist/styles/recipes/inline-attachment.css +9 -4
- package/dist/styles/recipes/inline-citation.css +10 -4
- package/dist/styles/recipes/item.css +23 -9
- package/dist/styles/recipes/markdown-block.css +5 -4
- package/dist/styles/recipes/markdown.css +68 -26
- package/dist/styles/recipes/morphing-panel.css +8 -8
- package/dist/styles/recipes/page-layout.css +36 -10
- package/dist/styles/recipes/pagination.css +5 -6
- package/dist/styles/recipes/phone-input.css +12 -8
- package/dist/styles/recipes/popup-controls.css +175 -26
- package/dist/styles/recipes/popup-drawer.css +35 -44
- package/dist/styles/recipes/popup-lists.css +139 -29
- package/dist/styles/recipes/popup-motion.css +4 -45
- package/dist/styles/recipes/popup-toast.css +55 -26
- package/dist/styles/recipes/popup.css +149 -153
- package/dist/styles/recipes/range.css +55 -19
- package/dist/styles/recipes/resizable.css +36 -7
- package/dist/styles/recipes/rich-tooltip.css +24 -34
- package/dist/styles/recipes/scroll-area.css +8 -1
- package/dist/styles/recipes/sidebar-menu.css +149 -34
- package/dist/styles/recipes/sidebar-parts.css +154 -0
- package/dist/styles/recipes/sidebar.css +187 -128
- package/dist/styles/recipes/source-badge.css +1 -1
- package/dist/styles/recipes/spinner.css +12 -1
- package/dist/styles/recipes/stepper.css +31 -13
- package/dist/styles/recipes/switch.css +68 -23
- package/dist/styles/recipes/table-of-contents.css +49 -41
- package/dist/styles/recipes/table.css +14 -10
- package/dist/styles/recipes/tabs.css +38 -12
- package/dist/styles/recipes/task-list.css +7 -14
- package/dist/styles/recipes/thread-rail.css +42 -19
- package/dist/styles/recipes/timeline.css +2 -0
- package/dist/styles/recipes/toolbar.css +27 -25
- package/dist/styles/recipes/track-highlight.css +12 -8
- package/dist/styles/recipes/transcript-divider.css +2 -2
- package/dist/styles/recipes/tree.css +31 -4
- package/dist/styles/recipes/user-ask-details.css +0 -1
- package/dist/styles/recipes/user-ask.css +4 -4
- package/dist/styles/theme.css +287 -107
- package/dist/task-list.d.ts +4 -1
- package/dist/task-list.js +24 -8
- package/dist/thread-rail.d.ts +1 -1
- package/dist/thread-rail.js +61 -19
- package/dist/transcript-divider.js +2 -1
- package/dist/ui/alert.d.ts +3 -1
- package/dist/ui/alert.js +14 -8
- package/dist/ui/autocomplete.js +7 -1
- package/dist/ui/avatar.d.ts +8 -0
- package/dist/ui/avatar.js +23 -3
- package/dist/ui/badge.js +1 -1
- package/dist/ui/breadcrumb.d.ts +27 -0
- package/dist/ui/breadcrumb.js +88 -0
- package/dist/ui/button.d.ts +2 -1
- package/dist/ui/button.js +1 -1
- package/dist/ui/calendar.js +2 -2
- package/dist/ui/card.d.ts +3 -1
- package/dist/ui/card.js +12 -7
- package/dist/ui/checkbox-group.js +1 -1
- package/dist/ui/checkbox.d.ts +2 -0
- package/dist/ui/code-diff.d.ts +1 -1
- package/dist/ui/code-diff.js +71 -27
- package/dist/ui/code.d.ts +5 -3
- package/dist/ui/code.js +27 -28
- package/dist/ui/color-picker.d.ts +5 -2
- package/dist/ui/color-picker.js +84 -36
- package/dist/ui/combobox-disabled-selection.d.ts +1 -2
- package/dist/ui/combobox-disabled-selection.js +7 -8
- package/dist/ui/combobox.d.ts +5 -23
- package/dist/ui/combobox.js +36 -16
- package/dist/ui/command.d.ts +8 -2
- package/dist/ui/command.js +39 -15
- package/dist/ui/context-menu.js +3 -0
- package/dist/ui/dialog.d.ts +2 -1
- package/dist/ui/dialog.js +3 -22
- package/dist/ui/dockable-panel-geometry.d.ts +1 -0
- package/dist/ui/dockable-panel-geometry.js +7 -1
- package/dist/ui/dockable-panel.d.ts +9 -6
- package/dist/ui/dockable-panel.js +81 -33
- package/dist/ui/drawer.d.ts +7 -4
- package/dist/ui/drawer.js +35 -22
- package/dist/ui/dropdown-menu.js +4 -3
- package/dist/ui/dropzone.d.ts +5 -3
- package/dist/ui/dropzone.js +78 -17
- package/dist/ui/emoji-picker.d.ts +18 -0
- package/dist/ui/emoji-picker.js +136 -0
- package/dist/ui/empty.d.ts +3 -1
- package/dist/ui/empty.js +12 -7
- package/dist/ui/field.js +1 -1
- package/dist/ui/gradient-editor.d.ts +1 -1
- package/dist/ui/gradient-editor.js +37 -21
- package/dist/ui/infinite-canvas.d.ts +13 -1
- package/dist/ui/infinite-canvas.js +139 -20
- package/dist/ui/input-group.d.ts +2 -4
- package/dist/ui/input-group.js +13 -14
- package/dist/ui/input-otp.d.ts +1 -1
- package/dist/ui/input-otp.js +32 -15
- package/dist/ui/input.js +11 -2
- package/dist/ui/item.d.ts +2 -1
- package/dist/ui/item.js +11 -7
- package/dist/ui/live-status.d.ts +7 -0
- package/dist/ui/live-status.js +17 -0
- package/dist/ui/markdown-elements.d.ts +6 -0
- package/dist/ui/markdown-elements.js +42 -9
- package/dist/ui/menubar.js +2 -2
- package/dist/ui/morphing-panel.js +2 -1
- package/dist/ui/native-select.d.ts +1 -1
- package/dist/ui/native-select.js +24 -11
- package/dist/ui/number-field.d.ts +1 -1
- package/dist/ui/number-field.js +13 -2
- package/dist/ui/page-layout.d.ts +2 -2
- package/dist/ui/page-layout.js +2 -4
- package/dist/ui/pagination.d.ts +7 -5
- package/dist/ui/pagination.js +19 -13
- package/dist/ui/phone-input.d.ts +1 -0
- package/dist/ui/phone-input.js +56 -58
- package/dist/ui/radio-group.d.ts +3 -0
- package/dist/ui/radio-group.js +11 -1
- package/dist/ui/resizable.d.ts +20 -2
- package/dist/ui/resizable.js +130 -8
- package/dist/ui/responsive-dialog.d.ts +1 -1
- package/dist/ui/responsive-dialog.js +9 -23
- package/dist/ui/rich-tooltip.d.ts +10 -4
- package/dist/ui/rich-tooltip.js +81 -33
- package/dist/ui/scroll-area.d.ts +4 -1
- package/dist/ui/scroll-area.js +44 -28
- package/dist/ui/select.d.ts +2 -1
- package/dist/ui/select.js +57 -32
- package/dist/ui/sidebar-menu.d.ts +5 -1
- package/dist/ui/sidebar-menu.js +31 -33
- package/dist/ui/sidebar-provider.d.ts +10 -1
- package/dist/ui/sidebar-provider.js +38 -13
- package/dist/ui/sidebar-resize-rail.js +7 -3
- package/dist/ui/sidebar.d.ts +11 -6
- package/dist/ui/sidebar.js +46 -23
- package/dist/ui/slider.js +31 -22
- package/dist/ui/spinner.d.ts +3 -2
- package/dist/ui/spinner.js +3 -2
- package/dist/ui/stepper.d.ts +8 -1
- package/dist/ui/stepper.js +74 -25
- package/dist/ui/switch.d.ts +1 -0
- package/dist/ui/table-of-contents.d.ts +5 -4
- package/dist/ui/table-of-contents.js +143 -52
- package/dist/ui/table.js +15 -9
- package/dist/ui/textarea.d.ts +1 -1
- package/dist/ui/textarea.js +20 -9
- package/dist/ui/timeline.d.ts +3 -1
- package/dist/ui/timeline.js +17 -9
- package/dist/ui/toast.d.ts +11 -5
- package/dist/ui/toast.js +100 -54
- package/dist/ui/toggle.d.ts +1 -1
- package/dist/ui/toggle.js +2 -1
- package/dist/ui/toolbar.d.ts +3 -2
- package/dist/ui/toolbar.js +4 -2
- package/dist/ui/tooltip.js +3 -20
- package/dist/ui/tree.js +120 -80
- package/dist/ui/trigger-menu.js +7 -2
- package/dist/user-ask.d.ts +10 -5
- package/dist/user-ask.js +31 -11
- package/package.json +5 -1
- package/dist/hooks/use-keyboard-navigation.d.ts +0 -3
- package/dist/hooks/use-keyboard-navigation.js +0 -22
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "./lib/cn.js";
|
|
3
3
|
import { Button } from "./ui/button.js";
|
|
4
|
+
import { LiveStatus } from "./ui/live-status.js";
|
|
4
5
|
import { ScrollArea } from "./ui/scroll-area.js";
|
|
5
6
|
import { Spinner } from "./ui/spinner.js";
|
|
6
|
-
import { createContext, useContext } from "react";
|
|
7
|
+
import { createContext, useContext, useLayoutEffect, useState } from "react";
|
|
7
8
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
import { XIcon } from "lucide-react";
|
|
9
10
|
//#region src/chat-composer-attachment.tsx
|
|
@@ -67,6 +68,24 @@ function kindFromAttachment(type, name) {
|
|
|
67
68
|
].includes(extension)) return "document";
|
|
68
69
|
return "file";
|
|
69
70
|
}
|
|
71
|
+
function resolveVariant(variant, kind, status, previewUrl) {
|
|
72
|
+
if (variant !== "auto") return variant;
|
|
73
|
+
if (status === "error") return "file";
|
|
74
|
+
return previewUrl || kind === "image" ? "preview" : "file";
|
|
75
|
+
}
|
|
76
|
+
function useObjectUrl(blob) {
|
|
77
|
+
const [objectUrl, setObjectUrl] = useState();
|
|
78
|
+
useLayoutEffect(() => {
|
|
79
|
+
if (!blob) return;
|
|
80
|
+
const url = URL.createObjectURL(blob);
|
|
81
|
+
setObjectUrl({
|
|
82
|
+
blob,
|
|
83
|
+
url
|
|
84
|
+
});
|
|
85
|
+
return () => URL.revokeObjectURL(url);
|
|
86
|
+
}, [blob]);
|
|
87
|
+
return blob && objectUrl?.blob === blob ? objectUrl.url : void 0;
|
|
88
|
+
}
|
|
70
89
|
function progressValue(progress) {
|
|
71
90
|
if (typeof progress !== "number" || Number.isNaN(progress)) return void 0;
|
|
72
91
|
return Math.min(100, Math.max(0, progress));
|
|
@@ -88,9 +107,27 @@ function defaultDescription(context) {
|
|
|
88
107
|
const { progress } = context;
|
|
89
108
|
if (context.description) return context.description;
|
|
90
109
|
if (context.status === "uploading") return progress === void 0 ? "Uploading…" : `Uploading ${Math.round(progress)}%`;
|
|
91
|
-
if (context.status === "error") return
|
|
110
|
+
if (context.status === "error") return context.errorLabel;
|
|
92
111
|
return fallbackLabel(context.kind, context.extension);
|
|
93
112
|
}
|
|
113
|
+
function defaultUploadedLabel(name) {
|
|
114
|
+
return `${name} uploaded`;
|
|
115
|
+
}
|
|
116
|
+
function defaultUploadFailedLabel(name) {
|
|
117
|
+
return `${name} couldn't upload`;
|
|
118
|
+
}
|
|
119
|
+
function useUploadAnnouncement(status, name, uploadedLabel, uploadFailedLabel) {
|
|
120
|
+
const [previousStatus, setPreviousStatus] = useState(status);
|
|
121
|
+
const [announcement, setAnnouncement] = useState("");
|
|
122
|
+
if (status !== previousStatus) {
|
|
123
|
+
setPreviousStatus(status);
|
|
124
|
+
setAnnouncement({
|
|
125
|
+
uploaded: uploadedLabel,
|
|
126
|
+
error: uploadFailedLabel
|
|
127
|
+
}[status]?.(name) ?? "");
|
|
128
|
+
}
|
|
129
|
+
return announcement;
|
|
130
|
+
}
|
|
94
131
|
function ChatComposerAttachments({ label = "Attachments", className, viewportClassName, children, ...props }) {
|
|
95
132
|
return /* @__PURE__ */ jsx("div", {
|
|
96
133
|
"data-control-ui": "chat-composer-attachments",
|
|
@@ -101,13 +138,12 @@ function ChatComposerAttachments({ label = "Attachments", className, viewportCla
|
|
|
101
138
|
children: /* @__PURE__ */ jsx(ScrollArea, {
|
|
102
139
|
"data-control-ui": "chat-composer-attachments",
|
|
103
140
|
"data-slot": "scroll",
|
|
104
|
-
"aria-label": label,
|
|
105
141
|
lockAxis: "y",
|
|
106
142
|
mask: true,
|
|
107
|
-
scrollbarVisibility: "hover",
|
|
108
143
|
className: "w-full",
|
|
109
144
|
viewportClassName,
|
|
110
145
|
children: /* @__PURE__ */ jsx("ul", {
|
|
146
|
+
"aria-label": label,
|
|
111
147
|
"data-control-ui": "chat-composer-attachments",
|
|
112
148
|
"data-control-family": "chat-composer-attachment",
|
|
113
149
|
"data-slot": "list",
|
|
@@ -117,13 +153,16 @@ function ChatComposerAttachments({ label = "Attachments", className, viewportCla
|
|
|
117
153
|
})
|
|
118
154
|
});
|
|
119
155
|
}
|
|
120
|
-
function ChatComposerAttachment({ name, type, description, status = "idle", progress: progressInput, previewUrl, kind, variant = "auto", onRemove, removeLabel, className, children,
|
|
156
|
+
function ChatComposerAttachment({ file, name: nameInput, type: typeInput, description, status = "idle", progress: progressInput, previewUrl: previewUrlInput, kind, variant = "auto", onRemove, removeLabel, errorLabel = "Couldn't upload. Remove and try again.", uploadedLabel = defaultUploadedLabel, uploadFailedLabel = defaultUploadFailedLabel, className, children, "aria-label": ariaLabel, ...props }) {
|
|
157
|
+
const name = nameInput ?? file?.name ?? "";
|
|
158
|
+
const type = typeInput ?? file?.type;
|
|
121
159
|
const resolvedKind = kind ?? kindFromAttachment(type, name);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
160
|
+
const resolvedVariant = resolveVariant(variant, resolvedKind, status, previewUrlInput);
|
|
161
|
+
const filePreviewUrl = useObjectUrl(resolvedVariant === "preview" && !previewUrlInput ? file : void 0);
|
|
162
|
+
const previewUrl = previewUrlInput ?? filePreviewUrl;
|
|
125
163
|
const extension = extensionFromName(name);
|
|
126
164
|
const progress = progressValue(progressInput);
|
|
165
|
+
const announcement = useUploadAnnouncement(status, name, uploadedLabel, uploadFailedLabel);
|
|
127
166
|
return /* @__PURE__ */ jsx(ChatComposerAttachmentContext.Provider, {
|
|
128
167
|
value: {
|
|
129
168
|
name,
|
|
@@ -136,10 +175,10 @@ function ChatComposerAttachment({ name, type, description, status = "idle", prog
|
|
|
136
175
|
variant: resolvedVariant,
|
|
137
176
|
extension,
|
|
138
177
|
onRemove,
|
|
139
|
-
removeLabel
|
|
178
|
+
removeLabel,
|
|
179
|
+
errorLabel
|
|
140
180
|
},
|
|
141
|
-
children: /* @__PURE__ */
|
|
142
|
-
role,
|
|
181
|
+
children: /* @__PURE__ */ jsxs("li", {
|
|
143
182
|
"aria-label": ariaLabel ?? name,
|
|
144
183
|
"data-control-ui": "chat-composer-attachment",
|
|
145
184
|
"data-control-family": "chat-composer-attachment",
|
|
@@ -150,12 +189,12 @@ function ChatComposerAttachment({ name, type, description, status = "idle", prog
|
|
|
150
189
|
"data-variant": resolvedVariant,
|
|
151
190
|
className: cn("relative isolate flex shrink-0 items-center overflow-hidden", className),
|
|
152
191
|
...props,
|
|
153
|
-
children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
192
|
+
children: [children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
154
193
|
/* @__PURE__ */ jsx(ChatComposerAttachmentPreview, {}),
|
|
155
194
|
resolvedVariant === "file" && /* @__PURE__ */ jsxs(ChatComposerAttachmentContent, { children: [/* @__PURE__ */ jsx(ChatComposerAttachmentTitle, {}), /* @__PURE__ */ jsx(ChatComposerAttachmentDescription, {})] }),
|
|
156
195
|
/* @__PURE__ */ jsx(ChatComposerAttachmentRemove, {}),
|
|
157
196
|
/* @__PURE__ */ jsx(ChatComposerAttachmentProgress, {})
|
|
158
|
-
] })
|
|
197
|
+
] }), /* @__PURE__ */ jsx(LiveStatus, { message: announcement })]
|
|
159
198
|
})
|
|
160
199
|
});
|
|
161
200
|
}
|
|
@@ -166,7 +205,7 @@ function ChatComposerAttachmentPreview({ className, children, ...props }) {
|
|
|
166
205
|
"data-control-ui": "chat-composer-attachment",
|
|
167
206
|
"data-control-family": "chat-composer-attachment",
|
|
168
207
|
"data-slot": "preview",
|
|
169
|
-
className: cn("relative
|
|
208
|
+
className: cn("relative flex shrink-0 items-center justify-center overflow-hidden", className),
|
|
170
209
|
...props,
|
|
171
210
|
children: children ?? (context.previewUrl ? /* @__PURE__ */ jsx("img", {
|
|
172
211
|
src: context.previewUrl,
|
|
@@ -194,25 +233,29 @@ function ChatComposerAttachmentContent({ className, ...props }) {
|
|
|
194
233
|
}
|
|
195
234
|
function ChatComposerAttachmentTitle({ className, children, ...props }) {
|
|
196
235
|
const { name } = useChatComposerAttachmentContext();
|
|
236
|
+
const content = children ?? name;
|
|
197
237
|
return /* @__PURE__ */ jsx("div", {
|
|
198
238
|
"data-control-ui": "chat-composer-attachment",
|
|
199
239
|
"data-control-family": "chat-composer-attachment",
|
|
200
240
|
"data-slot": "title",
|
|
241
|
+
title: typeof content === "string" ? content : void 0,
|
|
201
242
|
className: cn("truncate", className),
|
|
202
243
|
...props,
|
|
203
|
-
children:
|
|
244
|
+
children: content
|
|
204
245
|
});
|
|
205
246
|
}
|
|
206
247
|
function ChatComposerAttachmentDescription({ className, children, ...props }) {
|
|
207
248
|
const context = useChatComposerAttachmentContext();
|
|
249
|
+
const content = children ?? defaultDescription(context);
|
|
208
250
|
return /* @__PURE__ */ jsx("div", {
|
|
209
251
|
"data-control-ui": "chat-composer-attachment",
|
|
210
252
|
"data-control-family": "chat-composer-attachment",
|
|
211
253
|
"data-slot": "description",
|
|
254
|
+
title: typeof content === "string" ? content : void 0,
|
|
212
255
|
className: cn("truncate", className),
|
|
213
256
|
"data-state": context.status,
|
|
214
257
|
...props,
|
|
215
|
-
children:
|
|
258
|
+
children: content
|
|
216
259
|
});
|
|
217
260
|
}
|
|
218
261
|
function ChatComposerAttachmentRemove({ className, onClick, children, "aria-label": ariaLabel, ...props }) {
|
|
@@ -230,7 +273,7 @@ function ChatComposerAttachmentRemove({ className, onClick, children, "aria-labe
|
|
|
230
273
|
"aria-label": ariaLabel ?? removeLabel ?? `Remove ${name}`,
|
|
231
274
|
size: "xs",
|
|
232
275
|
variant: "solid",
|
|
233
|
-
className: cn("absolute
|
|
276
|
+
className: cn("absolute end-1 top-1 z-10", className),
|
|
234
277
|
onClick: handleClick,
|
|
235
278
|
...props,
|
|
236
279
|
children: children ?? /* @__PURE__ */ jsx(XIcon, {
|
|
@@ -239,7 +282,7 @@ function ChatComposerAttachmentRemove({ className, onClick, children, "aria-labe
|
|
|
239
282
|
})
|
|
240
283
|
});
|
|
241
284
|
}
|
|
242
|
-
function ChatComposerAttachmentProgress({ className, children,
|
|
285
|
+
function ChatComposerAttachmentProgress({ className, children, ...props }) {
|
|
243
286
|
const { name, status, progress } = useChatComposerAttachmentContext();
|
|
244
287
|
if (status !== "uploading") return null;
|
|
245
288
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -252,7 +295,6 @@ function ChatComposerAttachmentProgress({ className, children, style, ...props }
|
|
|
252
295
|
"data-control-family": "chat-composer-attachment",
|
|
253
296
|
"data-slot": "progress",
|
|
254
297
|
className: cn("pointer-events-none absolute inset-0 z-1 grid place-items-center", className),
|
|
255
|
-
style,
|
|
256
298
|
...props,
|
|
257
299
|
children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
258
300
|
"data-control-ui": "chat-composer-attachment",
|
|
@@ -8,6 +8,10 @@ export type MentionExtensionConfig<Item extends TriggerMenuItemData = TriggerMen
|
|
|
8
8
|
triggers: readonly TriggerConfig<Item>[];
|
|
9
9
|
side?: "top" | "bottom";
|
|
10
10
|
align?: "start" | "center" | "end";
|
|
11
|
+
/** Shown and announced when a trigger's query matches nothing. */
|
|
12
|
+
emptyLabel?: string;
|
|
13
|
+
/** Announced while the menu is open, so opening it and narrowing it are heard from the editor. */
|
|
14
|
+
resultsLabel?: (count: number) => string;
|
|
11
15
|
};
|
|
12
|
-
export declare function mentionExtension<Item extends TriggerMenuItemData = TriggerMenuItemData>({ triggers, side, align }: MentionExtensionConfig<Item>): ChatComposerEditorExtension;
|
|
16
|
+
export declare function mentionExtension<Item extends TriggerMenuItemData = TriggerMenuItemData>({ triggers, side, align, emptyLabel, resultsLabel }: MentionExtensionConfig<Item>): ChatComposerEditorExtension;
|
|
13
17
|
//#endregion
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { LiveStatus } from "../../ui/live-status.js";
|
|
3
|
+
import { isComposingKey, useTriggerMenu } from "../../hooks/use-trigger-menu.js";
|
|
2
4
|
import { spawnExitGhost } from "../ghost.js";
|
|
3
5
|
import { detectTrigger } from "../../lib/trigger-detect.js";
|
|
4
|
-
import { useTriggerMenu } from "../../hooks/use-trigger-menu.js";
|
|
5
6
|
import { TriggerMenu, TriggerMenuEmpty, TriggerMenuIcon, TriggerMenuItem, TriggerMenuList } from "../../ui/trigger-menu.js";
|
|
6
7
|
import { useEffect } from "react";
|
|
7
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
//#region src/chat-composer-editor/extensions/mention.tsx
|
|
9
10
|
function attrString(value, fallback) {
|
|
10
11
|
return typeof value === "string" ? value : fallback;
|
|
@@ -181,7 +182,7 @@ function collectMentions(doc) {
|
|
|
181
182
|
});
|
|
182
183
|
return mentions;
|
|
183
184
|
}
|
|
184
|
-
function MentionOverlay({ editor, triggers, side, align }) {
|
|
185
|
+
function MentionOverlay({ editor, triggers, side, align, emptyLabel, resultsLabel }) {
|
|
185
186
|
const chars = triggers.map((trigger) => trigger.char);
|
|
186
187
|
const controller = useTriggerMenu({
|
|
187
188
|
triggers,
|
|
@@ -220,8 +221,23 @@ function MentionOverlay({ editor, triggers, side, align }) {
|
|
|
220
221
|
controller,
|
|
221
222
|
chars
|
|
222
223
|
]);
|
|
224
|
+
const { "aria-controls": controls, "aria-activedescendant": activeDescendant } = controller.inputAria;
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
editor.setHostAria({
|
|
227
|
+
"aria-autocomplete": "list",
|
|
228
|
+
"aria-haspopup": "listbox",
|
|
229
|
+
...controls ? { "aria-controls": controls } : {},
|
|
230
|
+
...activeDescendant ? { "aria-activedescendant": activeDescendant } : {}
|
|
231
|
+
});
|
|
232
|
+
}, [
|
|
233
|
+
editor,
|
|
234
|
+
controls,
|
|
235
|
+
activeDescendant
|
|
236
|
+
]);
|
|
237
|
+
useEffect(() => () => editor.setHostAria({}), [editor]);
|
|
223
238
|
useEffect(() => {
|
|
224
239
|
return editor.registerKeyHandler((event) => {
|
|
240
|
+
if (isComposingKey(event)) return false;
|
|
225
241
|
const view = editor.getView();
|
|
226
242
|
if (!view) return false;
|
|
227
243
|
if (!readEditorTrigger(view, chars).active) return false;
|
|
@@ -233,35 +249,48 @@ function MentionOverlay({ editor, triggers, side, align }) {
|
|
|
233
249
|
chars
|
|
234
250
|
]);
|
|
235
251
|
if (triggers.length === 0) return null;
|
|
236
|
-
|
|
252
|
+
const isEmpty = controller.open && controller.items.length === 0;
|
|
253
|
+
let status = "";
|
|
254
|
+
if (isEmpty) status = emptyLabel;
|
|
255
|
+
else if (controller.open) status = resultsLabel(controller.items.length);
|
|
256
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LiveStatus, { message: status }), /* @__PURE__ */ jsxs(TriggerMenu, {
|
|
237
257
|
open: controller.open,
|
|
238
258
|
onOpenChange: controller.setOpen,
|
|
239
259
|
anchorRect: controller.anchorRect,
|
|
240
260
|
side,
|
|
241
261
|
align,
|
|
242
|
-
children:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
children:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
children: [isEmpty ? /* @__PURE__ */ jsx(TriggerMenuEmpty, {
|
|
263
|
+
"aria-hidden": "true",
|
|
264
|
+
children: emptyLabel
|
|
265
|
+
}) : null, /* @__PURE__ */ jsx(TriggerMenuList, {
|
|
266
|
+
id: controller.listId,
|
|
267
|
+
children: controller.items.map((item, index) => /* @__PURE__ */ jsxs(TriggerMenuItem, {
|
|
268
|
+
id: controller.optionId(index),
|
|
269
|
+
active: index === controller.activeIndex,
|
|
270
|
+
disabled: item.disabled,
|
|
271
|
+
onPointerMove: () => controller.setActiveIndex(index),
|
|
272
|
+
onClick: () => controller.select(item),
|
|
273
|
+
children: [
|
|
274
|
+
item.icon ? /* @__PURE__ */ jsx(TriggerMenuIcon, { children: item.icon }) : null,
|
|
275
|
+
/* @__PURE__ */ jsx("span", {
|
|
276
|
+
className: "flex-1 truncate",
|
|
277
|
+
title: item.label,
|
|
278
|
+
children: item.label
|
|
279
|
+
}),
|
|
280
|
+
item.description ? /* @__PURE__ */ jsx("span", {
|
|
281
|
+
"data-control-ui": "chat-composer",
|
|
282
|
+
"data-control-family": "chat-composer",
|
|
283
|
+
"data-slot": "mention-description",
|
|
284
|
+
className: "truncate",
|
|
285
|
+
title: item.description,
|
|
286
|
+
children: item.description
|
|
287
|
+
}) : null
|
|
288
|
+
]
|
|
289
|
+
}, item.id))
|
|
290
|
+
})]
|
|
291
|
+
})] });
|
|
263
292
|
}
|
|
264
|
-
function mentionExtension({ triggers, side = "top", align = "start" }) {
|
|
293
|
+
function mentionExtension({ triggers, side = "top", align = "start", emptyLabel = "No matches", resultsLabel = (count) => count === 1 ? "1 suggestion" : `${count} suggestions` }) {
|
|
265
294
|
return {
|
|
266
295
|
name: "mention",
|
|
267
296
|
nodes: { mention: mentionNode },
|
|
@@ -274,7 +303,9 @@ function mentionExtension({ triggers, side = "top", align = "start" }) {
|
|
|
274
303
|
editor,
|
|
275
304
|
triggers,
|
|
276
305
|
side,
|
|
277
|
-
align
|
|
306
|
+
align,
|
|
307
|
+
emptyLabel,
|
|
308
|
+
resultsLabel
|
|
278
309
|
})
|
|
279
310
|
};
|
|
280
311
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { prefersReducedMotion } from "../lib/motion.js";
|
|
2
3
|
//#region src/chat-composer-editor/ghost.ts
|
|
3
4
|
const MESSAGE_INHERIT = [
|
|
4
5
|
"font-family",
|
|
@@ -14,12 +15,9 @@ const MESSAGE_INHERIT = [
|
|
|
14
15
|
"word-break",
|
|
15
16
|
"text-align"
|
|
16
17
|
];
|
|
17
|
-
function prefersReducedMotion() {
|
|
18
|
-
return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
|
|
19
|
-
}
|
|
20
18
|
function spawnExitGhost(source, inherit = []) {
|
|
21
19
|
if (typeof document === "undefined") return;
|
|
22
|
-
if (prefersReducedMotion()) return;
|
|
20
|
+
if (prefersReducedMotion(source)) return;
|
|
23
21
|
const rect = source.getBoundingClientRect();
|
|
24
22
|
if (rect.width === 0 && rect.height === 0) return;
|
|
25
23
|
const ghost = document.createElement(source.tagName.toLowerCase());
|
|
@@ -8,6 +8,8 @@ export type ChatComposerEditorApi = {
|
|
|
8
8
|
getView: () => EditorView | null;
|
|
9
9
|
subscribe: (listener: () => void) => () => void;
|
|
10
10
|
registerKeyHandler: (handler: (event: KeyboardEvent) => boolean) => () => void;
|
|
11
|
+
/** Merges extra ARIA onto the editable host (e.g. combobox state while a trigger menu is open). */
|
|
12
|
+
setHostAria: (attributes: Readonly<Record<string, string>>) => void;
|
|
11
13
|
};
|
|
12
14
|
export type ChatComposerEditorOverlayProps = {
|
|
13
15
|
editor: ChatComposerEditorApi;
|
|
@@ -24,5 +26,8 @@ export type ChatComposerEditorProps = {
|
|
|
24
26
|
className?: string;
|
|
25
27
|
placeholder?: string;
|
|
26
28
|
extensions?: readonly ChatComposerEditorExtension[];
|
|
29
|
+
"aria-label"?: string;
|
|
30
|
+
"aria-labelledby"?: string;
|
|
31
|
+
"aria-describedby"?: string;
|
|
27
32
|
};
|
|
28
33
|
//#endregion
|
|
@@ -2,7 +2,7 @@ import { ChatComposerKnobStyle } from "./knob-contracts/chat-composer-knobs.js";
|
|
|
2
2
|
import { ChatComposerEditorProps } from "./chat-composer-editor/types.js";
|
|
3
3
|
import { CSSProperties } from "react";
|
|
4
4
|
//#region src/chat-composer-editor.d.ts
|
|
5
|
-
export declare function ChatComposerEditor({ className, placeholder, style, extensions }: ChatComposerEditorProps & {
|
|
5
|
+
export declare function ChatComposerEditor({ className, placeholder, style, extensions, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby }: ChatComposerEditorProps & {
|
|
6
6
|
style?: CSSProperties & ChatComposerKnobStyle;
|
|
7
7
|
}): import("react").JSX.Element;
|
|
8
8
|
//#endregion
|
|
@@ -4,7 +4,7 @@ import { useChatComposerContext } from "./chat-composer.js";
|
|
|
4
4
|
import { MESSAGE_GHOST_INHERIT, spawnExitGhost } from "./chat-composer-editor/ghost.js";
|
|
5
5
|
import { createEditorSchema } from "./chat-composer-editor/schema.js";
|
|
6
6
|
import { docFromText, serializeDoc } from "./chat-composer-editor/serialize.js";
|
|
7
|
-
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { baseKeymap, splitBlock } from "prosemirror-commands";
|
|
10
10
|
import { history, redo, undo } from "prosemirror-history";
|
|
@@ -12,8 +12,20 @@ import { keymap } from "prosemirror-keymap";
|
|
|
12
12
|
import { EditorState, Plugin } from "prosemirror-state";
|
|
13
13
|
import { EditorView } from "prosemirror-view";
|
|
14
14
|
//#region src/chat-composer-editor.tsx
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
function releaseFileDrag(_view, event) {
|
|
16
|
+
return event.dataTransfer ? Array.from(event.dataTransfer.types).includes("Files") : false;
|
|
17
|
+
}
|
|
18
|
+
/** 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. */
|
|
19
|
+
const fileTransferPassthrough = {
|
|
20
|
+
dragenter: releaseFileDrag,
|
|
21
|
+
dragover: releaseFileDrag,
|
|
22
|
+
drop: releaseFileDrag,
|
|
23
|
+
paste: (_view, event) => {
|
|
24
|
+
const types = event.clipboardData ? Array.from(event.clipboardData.types) : [];
|
|
25
|
+
return types.includes("Files") && !types.includes("text/plain");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function ChatComposerEditor({ className, placeholder, style, extensions = [], "aria-label": ariaLabel = "Message", "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby }) {
|
|
17
29
|
const input = useChatComposerContext();
|
|
18
30
|
const mountRef = useRef(null);
|
|
19
31
|
const viewRef = useRef(null);
|
|
@@ -23,6 +35,30 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
23
35
|
}, [input]);
|
|
24
36
|
const lastSerialized = useRef(input.value);
|
|
25
37
|
const [mounted, setMounted] = useState(false);
|
|
38
|
+
const [hostAria, setHostAria] = useState({});
|
|
39
|
+
const { isDisabled, isLocked } = input;
|
|
40
|
+
const hostAttributes = useMemo(() => ({
|
|
41
|
+
role: "textbox",
|
|
42
|
+
"aria-multiline": "true",
|
|
43
|
+
...ariaLabelledby ? { "aria-labelledby": ariaLabelledby } : { "aria-label": ariaLabel },
|
|
44
|
+
...ariaDescribedby ? { "aria-describedby": ariaDescribedby } : {},
|
|
45
|
+
...placeholder ? { "aria-placeholder": placeholder } : {},
|
|
46
|
+
...isDisabled ? { "aria-disabled": "true" } : {},
|
|
47
|
+
...isLocked ? {
|
|
48
|
+
"aria-readonly": "true",
|
|
49
|
+
"aria-busy": "true"
|
|
50
|
+
} : {},
|
|
51
|
+
...hostAria
|
|
52
|
+
}), [
|
|
53
|
+
ariaLabel,
|
|
54
|
+
ariaLabelledby,
|
|
55
|
+
ariaDescribedby,
|
|
56
|
+
placeholder,
|
|
57
|
+
isDisabled,
|
|
58
|
+
isLocked,
|
|
59
|
+
hostAria
|
|
60
|
+
]);
|
|
61
|
+
const hostAttributesRef = useRef(hostAttributes);
|
|
26
62
|
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
27
63
|
const keyHandlersRef = useRef(/* @__PURE__ */ new Set());
|
|
28
64
|
const [api] = useState(() => ({
|
|
@@ -38,7 +74,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
38
74
|
return () => {
|
|
39
75
|
keyHandlersRef.current.delete(handler);
|
|
40
76
|
};
|
|
41
|
-
}
|
|
77
|
+
},
|
|
78
|
+
setHostAria
|
|
42
79
|
}));
|
|
43
80
|
const [initialExtensions] = useState(extensions);
|
|
44
81
|
useEffect(() => {
|
|
@@ -55,14 +92,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
55
92
|
inputRef.current.submit(extra);
|
|
56
93
|
return true;
|
|
57
94
|
};
|
|
95
|
+
const submitOnEnter = (editorState) => inputRef.current.submitKey === "enter" && submitMessage(editorState);
|
|
58
96
|
const state = EditorState.create({
|
|
59
97
|
schema,
|
|
60
98
|
doc: docFromText(schema, inputRef.current.value),
|
|
61
99
|
plugins: [
|
|
62
|
-
new Plugin({
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
100
|
+
new Plugin({
|
|
101
|
+
props: {
|
|
102
|
+
handleKeyDown: (_view, event) => {
|
|
103
|
+
for (const handler of keyHandlersRef.current) if (handler(event)) return true;
|
|
104
|
+
return false;
|
|
105
|
+
},
|
|
106
|
+
handleDOMEvents: fileTransferPassthrough
|
|
107
|
+
},
|
|
108
|
+
filterTransaction: (transaction) => !transaction.docChanged || !inputRef.current.isLocked
|
|
109
|
+
}),
|
|
66
110
|
...extensionPlugins,
|
|
67
111
|
history(),
|
|
68
112
|
keymap(extensionKeymap),
|
|
@@ -71,7 +115,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
71
115
|
"Mod-y": redo,
|
|
72
116
|
"Shift-Mod-z": redo,
|
|
73
117
|
"Shift-Enter": splitBlock,
|
|
74
|
-
|
|
118
|
+
Enter: submitOnEnter,
|
|
119
|
+
"Mod-Enter": submitMessage
|
|
75
120
|
}),
|
|
76
121
|
keymap(baseKeymap)
|
|
77
122
|
]
|
|
@@ -79,11 +124,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
79
124
|
const view = new EditorView(mount, {
|
|
80
125
|
state,
|
|
81
126
|
editable: () => !inputRef.current.isDisabled,
|
|
82
|
-
attributes:
|
|
83
|
-
"aria-label": "Message",
|
|
84
|
-
"aria-multiline": "true",
|
|
85
|
-
role: "textbox"
|
|
86
|
-
},
|
|
127
|
+
attributes: () => hostAttributesRef.current,
|
|
87
128
|
dispatchTransaction(transaction) {
|
|
88
129
|
const next = view.state.apply(transaction);
|
|
89
130
|
view.updateState(next);
|
|
@@ -102,6 +143,13 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
102
143
|
viewRef.current = null;
|
|
103
144
|
};
|
|
104
145
|
}, [api, initialExtensions]);
|
|
146
|
+
const { registerInsertionTarget } = input;
|
|
147
|
+
useEffect(() => registerInsertionTarget((text) => {
|
|
148
|
+
const view = viewRef.current;
|
|
149
|
+
if (!view) return;
|
|
150
|
+
view.dispatch(view.state.tr.insertText(text));
|
|
151
|
+
view.focus();
|
|
152
|
+
}), [registerInsertionTarget]);
|
|
105
153
|
useEffect(() => {
|
|
106
154
|
const view = viewRef.current;
|
|
107
155
|
if (!view) return;
|
|
@@ -116,16 +164,9 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
116
164
|
}));
|
|
117
165
|
}, [input.value]);
|
|
118
166
|
useEffect(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"aria-label": "Message",
|
|
123
|
-
"aria-multiline": "true",
|
|
124
|
-
"aria-disabled": String(input.isDisabled),
|
|
125
|
-
role: "textbox"
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}, [input.isDisabled]);
|
|
167
|
+
hostAttributesRef.current = hostAttributes;
|
|
168
|
+
viewRef.current?.setProps({});
|
|
169
|
+
}, [hostAttributes]);
|
|
129
170
|
return /* @__PURE__ */ jsxs("div", {
|
|
130
171
|
"data-control-ui": "chat-composer-editor",
|
|
131
172
|
"data-control-family": "chat-composer",
|
|
@@ -138,7 +179,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
138
179
|
"data-control-ui": "chat-composer-editor",
|
|
139
180
|
"data-control-family": "chat-composer",
|
|
140
181
|
"data-slot": "placeholder",
|
|
141
|
-
className: "pointer-events-none absolute
|
|
182
|
+
className: "pointer-events-none absolute start-[var(--padding-x)] top-[var(--padding-y)]",
|
|
142
183
|
children: placeholder
|
|
143
184
|
}) : null,
|
|
144
185
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -146,19 +187,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
146
187
|
"data-control-ui": "chat-composer-editor",
|
|
147
188
|
"data-control-family": "chat-composer",
|
|
148
189
|
"data-slot": "editor",
|
|
149
|
-
className: cn("[&_.ProseMirror]:max-h-[40dvh] [&_.ProseMirror]:min-h-16 [&_.ProseMirror]:w-full [&_.ProseMirror]:overflow-y-auto [&_.ProseMirror]:
|
|
190
|
+
className: cn("[&_.ProseMirror]:max-h-[40dvh] [&_.ProseMirror]:min-h-16 [&_.ProseMirror]:w-full [&_.ProseMirror]:overflow-y-auto [&_.ProseMirror]:whitespace-pre-wrap [&_.ProseMirror]:break-words [&_.ProseMirror]:px-[var(--padding-x)] [&_.ProseMirror]:py-[var(--padding-y)]", mounted ? "" : "hidden")
|
|
150
191
|
}),
|
|
151
192
|
mounted ? null : /* @__PURE__ */ jsx("textarea", {
|
|
152
193
|
"data-control-ui": "chat-composer-editor",
|
|
153
194
|
"data-control-family": "chat-composer",
|
|
154
195
|
"data-slot": "fallback",
|
|
155
|
-
"aria-label":
|
|
196
|
+
"aria-label": ariaLabelledby ? void 0 : ariaLabel,
|
|
197
|
+
"aria-labelledby": ariaLabelledby,
|
|
198
|
+
"aria-describedby": ariaDescribedby,
|
|
156
199
|
defaultValue: input.value,
|
|
157
200
|
readOnly: true,
|
|
158
201
|
disabled: input.isDisabled,
|
|
159
202
|
rows: 2,
|
|
160
203
|
placeholder,
|
|
161
|
-
className: "min-h-16 w-full resize-none
|
|
204
|
+
className: "min-h-16 w-full resize-none px-[var(--padding-x)] py-[var(--padding-y)]"
|
|
162
205
|
}),
|
|
163
206
|
mounted ? initialExtensions.map((extension) => extension.Overlay ? /* @__PURE__ */ jsx(extension.Overlay, { editor: api }, extension.name) : null) : null
|
|
164
207
|
]
|
package/dist/chat-composer.d.ts
CHANGED
|
@@ -1,35 +1,10 @@
|
|
|
1
|
-
import { FormSubmitEvent } from "./control-props.js";
|
|
2
1
|
import { Button } from "./ui/button.js";
|
|
3
2
|
import { ChatComposerKnobStyle } from "./knob-contracts/chat-composer-knobs.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { CSSProperties, ComponentProps } from "react";
|
|
3
|
+
import { ChatComposerController, ChatComposerProps } from "./hooks/use-chat-composer.js";
|
|
4
|
+
import { CSSProperties, ComponentProps, ReactNode } from "react";
|
|
7
5
|
//#region src/chat-composer.d.ts
|
|
8
|
-
export declare function useChatComposerContext():
|
|
9
|
-
|
|
10
|
-
setValue: (nextValue: string) => void;
|
|
11
|
-
normalizedValue: string;
|
|
12
|
-
state: "disabled" | "idle" | "submitting";
|
|
13
|
-
density: ChatDensity;
|
|
14
|
-
isCompact: boolean;
|
|
15
|
-
isDisabled: boolean;
|
|
16
|
-
canSubmit: boolean;
|
|
17
|
-
rows: number;
|
|
18
|
-
sendCount: number;
|
|
19
|
-
clear: () => void;
|
|
20
|
-
submit: (extra?: Partial<ChatComposerSubmitPayload>) => void;
|
|
21
|
-
handleSubmit: (event: FormSubmitEvent) => void;
|
|
22
|
-
handleKeyDown: (event: {
|
|
23
|
-
key: string;
|
|
24
|
-
shiftKey: boolean;
|
|
25
|
-
defaultPrevented: boolean;
|
|
26
|
-
nativeEvent: {
|
|
27
|
-
isComposing: boolean;
|
|
28
|
-
};
|
|
29
|
-
preventDefault: () => void;
|
|
30
|
-
}) => void;
|
|
31
|
-
};
|
|
32
|
-
export declare function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, className, children, ...props }: ChatComposerProps): import("react").JSX.Element;
|
|
6
|
+
export declare function useChatComposerContext(): ChatComposerController;
|
|
7
|
+
export declare function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, allowEmptySubmit, submitKey, className, children, ...props }: ChatComposerProps): import("react").JSX.Element;
|
|
33
8
|
export type ChatComposerShellProps = Omit<ComponentProps<"div">, "style"> & {
|
|
34
9
|
style?: CSSProperties & ChatComposerKnobStyle;
|
|
35
10
|
};
|
|
@@ -41,7 +16,7 @@ export declare function ChatComposerAccent({ className, ...props }: ChatComposer
|
|
|
41
16
|
export type ChatComposerTextareaProps = Omit<ComponentProps<"textarea">, "style"> & {
|
|
42
17
|
style?: CSSProperties & ChatComposerKnobStyle;
|
|
43
18
|
};
|
|
44
|
-
export declare function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown, ...props }: ChatComposerTextareaProps): import("react").JSX.Element;
|
|
19
|
+
export declare function ChatComposerTextarea({ className, rows, disabled, readOnly, onChange, onKeyDown, ref, ...props }: ChatComposerTextareaProps): import("react").JSX.Element;
|
|
45
20
|
export type ChatComposerToolbarProps = ComponentProps<"div"> & {
|
|
46
21
|
style?: CSSProperties & ChatComposerKnobStyle;
|
|
47
22
|
};
|
|
@@ -54,6 +29,12 @@ export type ChatComposerFooterProps = Omit<ComponentProps<"div">, "style"> & {
|
|
|
54
29
|
style?: CSSProperties & ChatComposerKnobStyle;
|
|
55
30
|
};
|
|
56
31
|
export declare function ChatComposerFooter({ className, ...props }: ChatComposerFooterProps): import("react").JSX.Element;
|
|
57
|
-
export type ChatComposerSubmitProps = ComponentProps<typeof Button
|
|
58
|
-
|
|
32
|
+
export type ChatComposerSubmitProps = ComponentProps<typeof Button> & {
|
|
33
|
+
/** While the composer is submitting, turns this same button into Stop so focus stays put. */
|
|
34
|
+
onStop?: () => void;
|
|
35
|
+
stopLabel?: string;
|
|
36
|
+
/** Content of an `iconOnly` button in its Stop state; `stopLabel` becomes its name. */
|
|
37
|
+
stopIcon?: ReactNode;
|
|
38
|
+
};
|
|
39
|
+
export declare function ChatComposerSubmit({ className, disabled, iconOnly, children, onStop, stopLabel, stopIcon, onClick, "aria-label": ariaLabel, ...props }: ChatComposerSubmitProps): import("react").JSX.Element;
|
|
59
40
|
//#endregion
|