@ctrl-ui/react 0.8.7 → 0.8.9
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 +39 -39
- package/dist/blocks/design-canvas.d.ts +5 -1
- package/dist/blocks/design-canvas.js +198 -92
- 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 +4 -1
- package/dist/chat-composer-attachment.js +35 -10
- package/dist/chat-composer-editor/extensions/mention.d.ts +5 -1
- package/dist/chat-composer-editor/extensions/mention.js +59 -28
- 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 +57 -30
- package/dist/chat-composer.d.ts +13 -32
- package/dist/chat-composer.js +52 -12
- package/dist/chat-layout.d.ts +19 -0
- package/dist/chat-layout.js +91 -40
- 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/editor.js +20 -8
- package/dist/filter-bar/model.d.ts +1 -0
- package/dist/filter-bar/model.js +10 -1
- package/dist/filter-bar/motion.js +4 -4
- package/dist/filter-bar/parts.js +19 -8
- package/dist/filter-bar/types.d.ts +1 -0
- package/dist/filter-bar.js +19 -13
- package/dist/hooks/use-audio-recorder.js +12 -6
- package/dist/hooks/use-chat-composer.d.ts +19 -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-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 +36 -15
- 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-message-knobs.d.ts +1 -1
- package/dist/knob-contracts/chat-message-knobs.js +8 -1
- 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/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 +1 -1
- package/dist/knob-contracts/filter-bar-knobs.js +1 -2
- 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 +2 -1
- package/dist/knob-contracts/popup-knobs.d.ts +1 -1
- package/dist/knob-contracts/popup-knobs.js +27 -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 +2 -1
- 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 +2 -1
- package/dist/popup-parts.d.ts +8 -0
- package/dist/popup-parts.js +43 -0
- 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 +10 -5
- 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 +5 -7
- package/dist/styles/recipes/button-states.css +66 -0
- package/dist/styles/recipes/button.css +24 -83
- 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 +7 -10
- package/dist/styles/recipes/chat-layout.css +35 -0
- 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 +18 -21
- package/dist/styles/recipes/code-diff.css +46 -33
- package/dist/styles/recipes/code.css +36 -16
- package/dist/styles/recipes/collapsible.css +7 -2
- 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 +11 -2
- package/dist/styles/recipes/context.css +3 -3
- package/dist/styles/recipes/dockable-panel.css +7 -4
- 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 +30 -24
- package/dist/styles/recipes/field.css +61 -26
- package/dist/styles/recipes/filter-bar.css +18 -31
- package/dist/styles/recipes/gradient-editor.css +18 -7
- package/dist/styles/recipes/infinite-canvas.css +0 -1
- package/dist/styles/recipes/inline-attachment.css +3 -3
- 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 +20 -12
- package/dist/styles/recipes/pagination.css +4 -5
- package/dist/styles/recipes/phone-input.css +12 -8
- package/dist/styles/recipes/popup-controls.css +166 -33
- package/dist/styles/recipes/popup-drawer.css +3 -14
- package/dist/styles/recipes/popup-lists.css +128 -27
- package/dist/styles/recipes/popup-motion.css +4 -45
- package/dist/styles/recipes/popup-toast.css +55 -26
- package/dist/styles/recipes/popup.css +139 -141
- package/dist/styles/recipes/range.css +54 -18
- package/dist/styles/recipes/resizable.css +2 -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 +166 -34
- package/dist/styles/recipes/sidebar-parts.css +192 -0
- package/dist/styles/recipes/sidebar.css +188 -128
- package/dist/styles/recipes/source-badge.css +1 -1
- package/dist/styles/recipes/spinner.css +8 -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 +9 -1
- package/dist/styles/recipes/table.css +14 -10
- package/dist/styles/recipes/tabs.css +17 -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 +10 -7
- 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 +37 -16
- 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 +3 -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.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 +26 -27
- 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 -9
- package/dist/ui/combobox.js +13 -5
- package/dist/ui/command.d.ts +8 -2
- package/dist/ui/command.js +30 -14
- 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 +63 -23
- package/dist/ui/dropdown-menu.js +4 -3
- package/dist/ui/dropzone.d.ts +2 -2
- package/dist/ui/dropzone.js +75 -16
- 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/gradient-editor.d.ts +1 -1
- package/dist/ui/gradient-editor.js +37 -21
- package/dist/ui/infinite-canvas.d.ts +3 -2
- package/dist/ui/infinite-canvas.js +63 -19
- package/dist/ui/input-group.d.ts +0 -3
- package/dist/ui/input-group.js +3 -13
- package/dist/ui/input-otp.d.ts +1 -1
- package/dist/ui/input-otp.js +32 -15
- package/dist/ui/input.js +4 -4
- 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 +31 -1
- 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/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 +2 -0
- package/dist/ui/resizable.d.ts +3 -2
- package/dist/ui/resizable.js +5 -3
- package/dist/ui/responsive-dialog.d.ts +1 -1
- package/dist/ui/responsive-dialog.js +4 -21
- package/dist/ui/rich-tooltip.d.ts +10 -4
- package/dist/ui/rich-tooltip.js +81 -33
- package/dist/ui/scroll-area.d.ts +1 -1
- package/dist/ui/scroll-area.js +43 -28
- package/dist/ui/sidebar-menu.d.ts +5 -1
- package/dist/ui/sidebar-menu.js +34 -34
- 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 +48 -24
- 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 +2 -2
- package/dist/ui/table-of-contents.js +20 -8
- package/dist/ui/table.js +4 -2
- 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/toolbar.d.ts +2 -1
- package/dist/ui/toolbar.js +3 -1
- package/dist/ui/tooltip.js +3 -20
- package/dist/ui/tree.js +120 -80
- package/dist/ui/trigger-menu.js +1 -0
- 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,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
|
-
import { detectTrigger } from "../../lib/trigger-detect.js";
|
|
4
|
-
import { useTriggerMenu } from "../../hooks/use-trigger-menu.js";
|
|
5
5
|
import { TriggerMenu, TriggerMenuEmpty, TriggerMenuIcon, TriggerMenuItem, TriggerMenuList } from "../../ui/trigger-menu.js";
|
|
6
|
+
import { detectTrigger } from "../../lib/trigger-detect.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,7 +12,6 @@ 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
|
-
const SUBMIT_KEY = "Enter";
|
|
16
15
|
function releaseFileDrag(_view, event) {
|
|
17
16
|
return event.dataTransfer ? Array.from(event.dataTransfer.types).includes("Files") : false;
|
|
18
17
|
}
|
|
@@ -26,7 +25,7 @@ const fileTransferPassthrough = {
|
|
|
26
25
|
return types.includes("Files") && !types.includes("text/plain");
|
|
27
26
|
}
|
|
28
27
|
};
|
|
29
|
-
function ChatComposerEditor({ className, placeholder, style, extensions = [] }) {
|
|
28
|
+
function ChatComposerEditor({ className, placeholder, style, extensions = [], "aria-label": ariaLabel = "Message", "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby }) {
|
|
30
29
|
const input = useChatComposerContext();
|
|
31
30
|
const mountRef = useRef(null);
|
|
32
31
|
const viewRef = useRef(null);
|
|
@@ -36,6 +35,30 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
36
35
|
}, [input]);
|
|
37
36
|
const lastSerialized = useRef(input.value);
|
|
38
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);
|
|
39
62
|
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
40
63
|
const keyHandlersRef = useRef(/* @__PURE__ */ new Set());
|
|
41
64
|
const [api] = useState(() => ({
|
|
@@ -51,7 +74,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
51
74
|
return () => {
|
|
52
75
|
keyHandlersRef.current.delete(handler);
|
|
53
76
|
};
|
|
54
|
-
}
|
|
77
|
+
},
|
|
78
|
+
setHostAria
|
|
55
79
|
}));
|
|
56
80
|
const [initialExtensions] = useState(extensions);
|
|
57
81
|
useEffect(() => {
|
|
@@ -68,17 +92,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
68
92
|
inputRef.current.submit(extra);
|
|
69
93
|
return true;
|
|
70
94
|
};
|
|
95
|
+
const submitOnEnter = (editorState) => inputRef.current.submitKey === "enter" && submitMessage(editorState);
|
|
71
96
|
const state = EditorState.create({
|
|
72
97
|
schema,
|
|
73
98
|
doc: docFromText(schema, inputRef.current.value),
|
|
74
99
|
plugins: [
|
|
75
|
-
new Plugin({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
79
107
|
},
|
|
80
|
-
|
|
81
|
-
}
|
|
108
|
+
filterTransaction: (transaction) => !transaction.docChanged || !inputRef.current.isLocked
|
|
109
|
+
}),
|
|
82
110
|
...extensionPlugins,
|
|
83
111
|
history(),
|
|
84
112
|
keymap(extensionKeymap),
|
|
@@ -87,7 +115,8 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
87
115
|
"Mod-y": redo,
|
|
88
116
|
"Shift-Mod-z": redo,
|
|
89
117
|
"Shift-Enter": splitBlock,
|
|
90
|
-
|
|
118
|
+
Enter: submitOnEnter,
|
|
119
|
+
"Mod-Enter": submitMessage
|
|
91
120
|
}),
|
|
92
121
|
keymap(baseKeymap)
|
|
93
122
|
]
|
|
@@ -95,11 +124,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
95
124
|
const view = new EditorView(mount, {
|
|
96
125
|
state,
|
|
97
126
|
editable: () => !inputRef.current.isDisabled,
|
|
98
|
-
attributes:
|
|
99
|
-
"aria-label": "Message",
|
|
100
|
-
"aria-multiline": "true",
|
|
101
|
-
role: "textbox"
|
|
102
|
-
},
|
|
127
|
+
attributes: () => hostAttributesRef.current,
|
|
103
128
|
dispatchTransaction(transaction) {
|
|
104
129
|
const next = view.state.apply(transaction);
|
|
105
130
|
view.updateState(next);
|
|
@@ -118,6 +143,13 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
118
143
|
viewRef.current = null;
|
|
119
144
|
};
|
|
120
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]);
|
|
121
153
|
useEffect(() => {
|
|
122
154
|
const view = viewRef.current;
|
|
123
155
|
if (!view) return;
|
|
@@ -132,16 +164,9 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
132
164
|
}));
|
|
133
165
|
}, [input.value]);
|
|
134
166
|
useEffect(() => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"aria-label": "Message",
|
|
139
|
-
"aria-multiline": "true",
|
|
140
|
-
"aria-disabled": String(input.isDisabled),
|
|
141
|
-
role: "textbox"
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}, [input.isDisabled]);
|
|
167
|
+
hostAttributesRef.current = hostAttributes;
|
|
168
|
+
viewRef.current?.setProps({});
|
|
169
|
+
}, [hostAttributes]);
|
|
145
170
|
return /* @__PURE__ */ jsxs("div", {
|
|
146
171
|
"data-control-ui": "chat-composer-editor",
|
|
147
172
|
"data-control-family": "chat-composer",
|
|
@@ -154,7 +179,7 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
154
179
|
"data-control-ui": "chat-composer-editor",
|
|
155
180
|
"data-control-family": "chat-composer",
|
|
156
181
|
"data-slot": "placeholder",
|
|
157
|
-
className: "pointer-events-none absolute
|
|
182
|
+
className: "pointer-events-none absolute start-[var(--padding-x)] top-[var(--padding-y)]",
|
|
158
183
|
children: placeholder
|
|
159
184
|
}) : null,
|
|
160
185
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -162,19 +187,21 @@ function ChatComposerEditor({ className, placeholder, style, extensions = [] })
|
|
|
162
187
|
"data-control-ui": "chat-composer-editor",
|
|
163
188
|
"data-control-family": "chat-composer",
|
|
164
189
|
"data-slot": "editor",
|
|
165
|
-
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")
|
|
166
191
|
}),
|
|
167
192
|
mounted ? null : /* @__PURE__ */ jsx("textarea", {
|
|
168
193
|
"data-control-ui": "chat-composer-editor",
|
|
169
194
|
"data-control-family": "chat-composer",
|
|
170
195
|
"data-slot": "fallback",
|
|
171
|
-
"aria-label":
|
|
196
|
+
"aria-label": ariaLabelledby ? void 0 : ariaLabel,
|
|
197
|
+
"aria-labelledby": ariaLabelledby,
|
|
198
|
+
"aria-describedby": ariaDescribedby,
|
|
172
199
|
defaultValue: input.value,
|
|
173
200
|
readOnly: true,
|
|
174
201
|
disabled: input.isDisabled,
|
|
175
202
|
rows: 2,
|
|
176
203
|
placeholder,
|
|
177
|
-
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)]"
|
|
178
205
|
}),
|
|
179
206
|
mounted ? initialExtensions.map((extension) => extension.Overlay ? /* @__PURE__ */ jsx(extension.Overlay, { editor: api }, extension.name) : null) : null
|
|
180
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, allowEmptySubmit, 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
|
package/dist/chat-composer.js
CHANGED
|
@@ -3,10 +3,11 @@ import { hasSkinAdornment, skinAdornment } from "./skin.js";
|
|
|
3
3
|
import { cn } from "./lib/cn.js";
|
|
4
4
|
import { useSkin } from "./skin-provider.js";
|
|
5
5
|
import { Button } from "./ui/button.js";
|
|
6
|
+
import { Spinner } from "./ui/spinner.js";
|
|
6
7
|
import { useChatComposer } from "./hooks/use-chat-composer.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { createContext, useContext, useEffect, useRef } from "react";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { Square } from "lucide-react";
|
|
10
11
|
//#region src/chat-composer.tsx
|
|
11
12
|
const ChatComposerContext = createContext(null);
|
|
12
13
|
function useChatComposerContext() {
|
|
@@ -14,7 +15,7 @@ function useChatComposerContext() {
|
|
|
14
15
|
if (!context) throw new Error("ChatComposer compound components must be rendered inside <ChatComposer>.");
|
|
15
16
|
return context;
|
|
16
17
|
}
|
|
17
|
-
function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, allowEmptySubmit = false, className, children, ...props }) {
|
|
18
|
+
function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, allowEmptySubmit = false, submitKey = "enter", className, children, ...props }) {
|
|
18
19
|
const skin = useSkin();
|
|
19
20
|
const input = useChatComposer({
|
|
20
21
|
value,
|
|
@@ -25,6 +26,7 @@ function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "i
|
|
|
25
26
|
density,
|
|
26
27
|
disabled,
|
|
27
28
|
allowEmptySubmit,
|
|
29
|
+
submitKey,
|
|
28
30
|
trackSends: hasSkinAdornment(skin, "chat-composer", "send-layer")
|
|
29
31
|
});
|
|
30
32
|
const sendLayer = skinAdornment(skin, "chat-composer", "send-layer", { sendCount: input.sendCount });
|
|
@@ -49,9 +51,7 @@ function ChatComposer({ value, defaultValue, onValueChange, onSubmit, state = "i
|
|
|
49
51
|
}
|
|
50
52
|
function ChatComposerShell({ className, ...props }) {
|
|
51
53
|
const input = useChatComposerContext();
|
|
52
|
-
const keyboardNavigation = useKeyboardNavigation();
|
|
53
54
|
return /* @__PURE__ */ jsx("div", {
|
|
54
|
-
"data-keyboard-navigation": keyboardNavigation ? "" : void 0,
|
|
55
55
|
"data-control-ui": "chat-composer",
|
|
56
56
|
"data-control-family": "chat-composer",
|
|
57
57
|
"data-slot": "shell",
|
|
@@ -71,8 +71,17 @@ function ChatComposerAccent({ className, ...props }) {
|
|
|
71
71
|
...props
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown, ...props }) {
|
|
74
|
+
function ChatComposerTextarea({ className, rows, disabled, readOnly, onChange, onKeyDown, ref, ...props }) {
|
|
75
75
|
const input = useChatComposerContext();
|
|
76
|
+
const textareaRef = useRef(null);
|
|
77
|
+
const { registerInsertionTarget } = input;
|
|
78
|
+
useEffect(() => registerInsertionTarget((text) => {
|
|
79
|
+
const textarea = textareaRef.current;
|
|
80
|
+
if (!textarea) return;
|
|
81
|
+
textarea.focus();
|
|
82
|
+
textarea.setRangeText(text, textarea.selectionStart, textarea.selectionEnd, "end");
|
|
83
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
84
|
+
}), [registerInsertionTarget]);
|
|
76
85
|
function handleChange(event) {
|
|
77
86
|
onChange?.(event);
|
|
78
87
|
if (!event.defaultPrevented) input.setValue(event.currentTarget.value);
|
|
@@ -81,7 +90,12 @@ function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown,
|
|
|
81
90
|
onKeyDown?.(event);
|
|
82
91
|
input.handleKeyDown(event);
|
|
83
92
|
}
|
|
93
|
+
function attachTextarea(node) {
|
|
94
|
+
textareaRef.current = node;
|
|
95
|
+
assignRef(ref, node);
|
|
96
|
+
}
|
|
84
97
|
return /* @__PURE__ */ jsx("textarea", {
|
|
98
|
+
ref: attachTextarea,
|
|
85
99
|
"data-control-ui": "chat-composer",
|
|
86
100
|
"data-control-family": "chat-composer",
|
|
87
101
|
"data-slot": "textarea",
|
|
@@ -91,8 +105,10 @@ function ChatComposerTextarea({ className, rows, disabled, onChange, onKeyDown,
|
|
|
91
105
|
onChange: handleChange,
|
|
92
106
|
onKeyDown: handleKeyDown,
|
|
93
107
|
disabled: disabled || input.isDisabled,
|
|
108
|
+
readOnly: readOnly || input.isLocked,
|
|
109
|
+
"aria-busy": input.isLocked || void 0,
|
|
94
110
|
rows: rows ?? input.rows,
|
|
95
|
-
className: cn("field-sizing-content min-h-16 max-h-[40dvh] w-full resize-none
|
|
111
|
+
className: cn("field-sizing-content min-h-16 max-h-[40dvh] w-full resize-none px-[var(--padding-x)] py-[var(--padding-y)] disabled:cursor-not-allowed", className)
|
|
96
112
|
});
|
|
97
113
|
}
|
|
98
114
|
function ChatComposerToolbar({ className, ...props }) {
|
|
@@ -122,20 +138,44 @@ function ChatComposerFooter({ className, ...props }) {
|
|
|
122
138
|
...props
|
|
123
139
|
});
|
|
124
140
|
}
|
|
125
|
-
function ChatComposerSubmit({ className, disabled, children = "Send",
|
|
141
|
+
function ChatComposerSubmit({ className, disabled, iconOnly = false, children = "Send", onStop, stopLabel = "Stop response", stopIcon = /* @__PURE__ */ jsx(Square, {
|
|
142
|
+
"aria-hidden": "true",
|
|
143
|
+
className: "fill-current"
|
|
144
|
+
}), onClick, "aria-label": ariaLabel, ...props }) {
|
|
126
145
|
const input = useChatComposerContext();
|
|
146
|
+
const isStop = input.isLocked && onStop !== void 0;
|
|
147
|
+
const isBusy = input.isLocked && !isStop;
|
|
148
|
+
function handleClick(event) {
|
|
149
|
+
onClick?.(event);
|
|
150
|
+
if (isStop && !event.defaultPrevented) onStop();
|
|
151
|
+
}
|
|
152
|
+
function content() {
|
|
153
|
+
if (isStop) return iconOnly ? stopIcon : stopLabel;
|
|
154
|
+
if (!isBusy) return children;
|
|
155
|
+
if (iconOnly) return /* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true" });
|
|
156
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true" }), children] });
|
|
157
|
+
}
|
|
127
158
|
return /* @__PURE__ */ jsx(Button, {
|
|
128
159
|
"data-control-ui": "chat-composer",
|
|
129
160
|
"data-slot": "submit",
|
|
130
|
-
|
|
161
|
+
"data-stop": isStop ? "true" : void 0,
|
|
162
|
+
type: isStop ? "button" : "submit",
|
|
131
163
|
variant: "solid",
|
|
132
164
|
tone: "primary",
|
|
133
165
|
size: "xs",
|
|
134
|
-
|
|
166
|
+
iconOnly,
|
|
167
|
+
disabled: isStop ? disabled : disabled || !input.canSubmit,
|
|
168
|
+
"aria-busy": isBusy || void 0,
|
|
169
|
+
"aria-label": isStop && iconOnly ? stopLabel : ariaLabel,
|
|
170
|
+
onClick: handleClick,
|
|
135
171
|
className,
|
|
136
172
|
...props,
|
|
137
|
-
children
|
|
173
|
+
children: content()
|
|
138
174
|
});
|
|
139
175
|
}
|
|
176
|
+
function assignRef(ref, node) {
|
|
177
|
+
if (typeof ref === "function") ref(node);
|
|
178
|
+
else if (ref) ref.current = node;
|
|
179
|
+
}
|
|
140
180
|
//#endregion
|
|
141
181
|
export { ChatComposer, ChatComposerAccent, ChatComposerFooter, ChatComposerShell, ChatComposerSubmit, ChatComposerTextarea, ChatComposerToolbar, ChatComposerTools, useChatComposerContext };
|
package/dist/chat-layout.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RenderProp } from "./control-props.js";
|
|
1
2
|
import { Button } from "./ui/button.js";
|
|
2
3
|
import { ChatLayoutKnobStyle } from "./knob-contracts/chat-layout-knobs.js";
|
|
3
4
|
import { CSSProperties, ComponentProps, ReactNode } from "react";
|
|
@@ -8,6 +9,24 @@ export type ChatLayoutProps = Omit<ComponentProps<"section">, "style"> & {
|
|
|
8
9
|
style?: CSSProperties & ChatLayoutKnobStyle;
|
|
9
10
|
};
|
|
10
11
|
export declare function ChatLayout({ children, chrome, className, ...props }: ChatLayoutProps): import("react").JSX.Element;
|
|
12
|
+
export type ChatLayoutHeaderProps = Omit<ComponentProps<"header">, "style"> & {
|
|
13
|
+
style?: CSSProperties & ChatLayoutKnobStyle;
|
|
14
|
+
};
|
|
15
|
+
export declare function ChatLayoutHeader({ className, ...props }: ChatLayoutHeaderProps): import("react").JSX.Element;
|
|
16
|
+
export type ChatLayoutTitleProps = Omit<ComponentProps<"div">, "style"> & {
|
|
17
|
+
render?: RenderProp<ComponentProps<"div">>;
|
|
18
|
+
style?: CSSProperties & ChatLayoutKnobStyle;
|
|
19
|
+
};
|
|
20
|
+
/** Renders a `div`; pass `render={(props) => <h2 {...props} />}` for the heading level the page needs. */
|
|
21
|
+
export declare function ChatLayoutTitle({ render, className, ...props }: ChatLayoutTitleProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
22
|
+
export type ChatLayoutDescriptionProps = Omit<ComponentProps<"p">, "style"> & {
|
|
23
|
+
style?: CSSProperties & ChatLayoutKnobStyle;
|
|
24
|
+
};
|
|
25
|
+
export declare function ChatLayoutDescription({ className, ...props }: ChatLayoutDescriptionProps): import("react").JSX.Element;
|
|
26
|
+
export type ChatLayoutActionsProps = Omit<ComponentProps<"div">, "style"> & {
|
|
27
|
+
style?: CSSProperties & ChatLayoutKnobStyle;
|
|
28
|
+
};
|
|
29
|
+
export declare function ChatLayoutActions({ className, ...props }: ChatLayoutActionsProps): import("react").JSX.Element;
|
|
11
30
|
export type ChatThreadProps = ComponentProps<"div"> & {
|
|
12
31
|
composer?: ReactNode;
|
|
13
32
|
};
|