mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-04 21:02:39 +02:00
Enhance PromptInputTextarea: Add external onKeyDown handler, improve backspace mention deletion logic, and update styling for better layout
This commit is contained in:
parent
68117ac8d2
commit
3e52bfda30
1 changed files with 19 additions and 8 deletions
|
|
@ -910,6 +910,7 @@ export const PromptInputTextarea = ({
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
placeholder = "What would you like to know?",
|
placeholder = "What would you like to know?",
|
||||||
|
onKeyDown: externalOnKeyDown,
|
||||||
...props
|
...props
|
||||||
}: PromptInputTextareaProps) => {
|
}: PromptInputTextareaProps) => {
|
||||||
const controller = useOptionalPromptInputController();
|
const controller = useOptionalPromptInputController();
|
||||||
|
|
@ -1027,10 +1028,11 @@ export const PromptInputTextarea = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle backspace to delete entire mention at once
|
// Handle backspace to delete entire mention at once
|
||||||
if (e.key === "Backspace" && controller) {
|
if (e.key === "Backspace") {
|
||||||
const textarea = e.currentTarget;
|
const textarea = e.currentTarget;
|
||||||
const cursorPos = textarea.selectionStart;
|
const cursorPos = textarea.selectionStart;
|
||||||
const selectionEnd = textarea.selectionEnd;
|
const selectionEnd = textarea.selectionEnd;
|
||||||
|
const textValue = controller?.textInput.value ?? textarea.value;
|
||||||
|
|
||||||
// Only handle if no text is selected (cursor is at a single position)
|
// Only handle if no text is selected (cursor is at a single position)
|
||||||
if (cursorPos === selectionEnd) {
|
if (cursorPos === selectionEnd) {
|
||||||
|
|
@ -1039,13 +1041,19 @@ export const PromptInputTextarea = ({
|
||||||
const mentionText = `@${label}`;
|
const mentionText = `@${label}`;
|
||||||
const startPos = cursorPos - mentionText.length;
|
const startPos = cursorPos - mentionText.length;
|
||||||
if (startPos >= 0) {
|
if (startPos >= 0) {
|
||||||
const textBefore = currentValue.substring(startPos, cursorPos);
|
const textBefore = textValue.substring(startPos, cursorPos);
|
||||||
if (textBefore === mentionText) {
|
if (textBefore === mentionText) {
|
||||||
// Check if it's at word boundary (start of string or preceded by whitespace)
|
// Check if it's at word boundary (start of string or preceded by whitespace)
|
||||||
if (startPos === 0 || /\s/.test(currentValue[startPos - 1])) {
|
if (startPos === 0 || /\s/.test(textValue[startPos - 1])) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newText = currentValue.substring(0, startPos) + currentValue.substring(cursorPos);
|
const newText = textValue.substring(0, startPos) + textValue.substring(cursorPos);
|
||||||
controller.textInput.setInput(newText);
|
if (controller) {
|
||||||
|
controller.textInput.setInput(newText);
|
||||||
|
} else {
|
||||||
|
// Fallback: directly set textarea value and trigger change
|
||||||
|
textarea.value = newText;
|
||||||
|
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
}
|
||||||
// Remove the mention from state
|
// Remove the mention from state
|
||||||
if (mentionsCtx) {
|
if (mentionsCtx) {
|
||||||
const mentionToRemove = mentionsCtx.mentions.find(m => m.displayName === label);
|
const mentionToRemove = mentionsCtx.mentions.find(m => m.displayName === label);
|
||||||
|
|
@ -1084,6 +1092,9 @@ export const PromptInputTextarea = ({
|
||||||
// Let the popover handle this
|
// Let the popover handle this
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call external handler if provided
|
||||||
|
externalOnKeyDown?.(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = (event) => {
|
const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = (event) => {
|
||||||
|
|
@ -1123,12 +1134,12 @@ export const PromptInputTextarea = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="relative contents">
|
<div ref={containerRef} className="relative flex flex-1 min-w-0">
|
||||||
{mentionHighlights.hasHighlights && (
|
{mentionHighlights.hasHighlights && (
|
||||||
<div
|
<div
|
||||||
ref={highlightRef}
|
ref={highlightRef}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pointer-events-none absolute inset-0 z-0 overflow-hidden whitespace-pre-wrap break-words px-3 py-3 text-sm text-transparent"
|
className="pointer-events-none absolute inset-0 z-0 overflow-hidden whitespace-pre-wrap break-words text-sm text-transparent"
|
||||||
>
|
>
|
||||||
{mentionHighlights.segments.map((segment, index) =>
|
{mentionHighlights.segments.map((segment, index) =>
|
||||||
segment.highlighted ? (
|
segment.highlighted ? (
|
||||||
|
|
@ -1146,7 +1157,7 @@ export const PromptInputTextarea = ({
|
||||||
)}
|
)}
|
||||||
<InputGroupTextarea
|
<InputGroupTextarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
className={cn("field-sizing-content max-h-48 min-h-10", className)}
|
className={cn("relative z-10 !p-0 field-sizing-content max-h-48 min-h-10", className)}
|
||||||
name="message"
|
name="message"
|
||||||
onCompositionEnd={() => setIsComposing(false)}
|
onCompositionEnd={() => setIsComposing(false)}
|
||||||
onCompositionStart={() => setIsComposing(true)}
|
onCompositionStart={() => setIsComposing(true)}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue