refactor: standardize status badges and fix composer whitespace handling
This commit is contained in:
@@ -57,7 +57,7 @@ export function hasDraftContent(input: {
|
||||
text: string;
|
||||
attachments: UserComposerAttachment[];
|
||||
}): boolean {
|
||||
return input.text.trim().length > 0 || input.attachments.length > 0;
|
||||
return input.text.length > 0 || input.attachments.length > 0;
|
||||
}
|
||||
|
||||
export function areAttachmentsEqual(input: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveDraftKey } from "./input-draft-core";
|
||||
import { hasDraftContent, resolveDraftKey } from "./input-draft-core";
|
||||
import {
|
||||
buildDraftCommandConfig,
|
||||
resolveEffectiveComposerModelId,
|
||||
@@ -26,6 +26,12 @@ describe("resolveDraftKey", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasDraftContent", () => {
|
||||
it("preserves whitespace-only text while the user is editing", () => {
|
||||
expect(hasDraftContent({ text: "\n\n\n\n\n", attachments: [] })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveEffectiveComposerModelId", () => {
|
||||
it("returns the selected model trimmed", () => {
|
||||
expect(
|
||||
|
||||
@@ -8,6 +8,7 @@ interface Args {
|
||||
onHeight: (height: number) => void;
|
||||
}
|
||||
|
||||
export function useComposerHeightMirror(_args: Args): void {
|
||||
export function useComposerHeightMirror(_args: Args): (_value: string) => void {
|
||||
// No-op on native: onContentSizeChange drives height natively.
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function useComposerHeightMirror({
|
||||
minHeight,
|
||||
maxHeight,
|
||||
onHeight,
|
||||
}: Args): void {
|
||||
}: Args): (value: string) => void {
|
||||
const paramsRef = useRef({ value, minHeight, maxHeight, onHeight });
|
||||
paramsRef.current = { value, minHeight, maxHeight, onHeight };
|
||||
|
||||
@@ -70,34 +70,36 @@ export function useComposerHeightMirror({
|
||||
};
|
||||
}, []);
|
||||
|
||||
const measure = useCallback(() => {
|
||||
const mirror = mirrorRef.current;
|
||||
const source = textareaRef.current;
|
||||
if (!mirror || !source || typeof window === "undefined") return;
|
||||
if (!(source instanceof HTMLElement)) return;
|
||||
const measure = useCallback(
|
||||
(text: string) => {
|
||||
const mirror = mirrorRef.current;
|
||||
const source = textareaRef.current;
|
||||
if (!mirror || !source || typeof window === "undefined") return;
|
||||
if (!(source instanceof HTMLElement)) return;
|
||||
|
||||
const cs = window.getComputedStyle(source);
|
||||
const ms = mirror.style;
|
||||
for (const prop of COPIED_STYLES) {
|
||||
ms[prop] = cs[prop];
|
||||
}
|
||||
ms.width = `${source.clientWidth}px`;
|
||||
const cs = window.getComputedStyle(source);
|
||||
const ms = mirror.style;
|
||||
for (const prop of COPIED_STYLES) {
|
||||
ms[prop] = cs[prop];
|
||||
}
|
||||
ms.width = `${source.clientWidth}px`;
|
||||
|
||||
const {
|
||||
value: currentValue,
|
||||
minHeight: currentMinHeight,
|
||||
maxHeight: currentMaxHeight,
|
||||
onHeight: currentOnHeight,
|
||||
} = paramsRef.current;
|
||||
// Trailing newline is collapsed by textarea measurement — pad with a space.
|
||||
mirror.value = currentValue.endsWith("\n") ? `${currentValue} ` : currentValue;
|
||||
const {
|
||||
minHeight: currentMinHeight,
|
||||
maxHeight: currentMaxHeight,
|
||||
onHeight: currentOnHeight,
|
||||
} = paramsRef.current;
|
||||
// Trailing newline is collapsed by textarea measurement — pad with a space.
|
||||
mirror.value = text.endsWith("\n") ? `${text} ` : text;
|
||||
|
||||
const next = Math.max(currentMinHeight, Math.min(currentMaxHeight, mirror.scrollHeight));
|
||||
currentOnHeight(next);
|
||||
}, [textareaRef]);
|
||||
const next = Math.max(currentMinHeight, Math.min(currentMaxHeight, mirror.scrollHeight));
|
||||
currentOnHeight(next);
|
||||
},
|
||||
[textareaRef],
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
measure();
|
||||
measure(value);
|
||||
}, [maxHeight, minHeight, value, measure]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -109,9 +111,11 @@ export function useComposerHeightMirror({
|
||||
const nextWidth = source.clientWidth;
|
||||
if (Math.abs(nextWidth - previousWidth) < 1) return;
|
||||
previousWidth = nextWidth;
|
||||
measure();
|
||||
measure(paramsRef.current.value);
|
||||
});
|
||||
observer.observe(source);
|
||||
return () => observer.disconnect();
|
||||
}, [textareaRef, measure]);
|
||||
|
||||
return measure;
|
||||
}
|
||||
|
||||
@@ -1586,7 +1586,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
setBoundedInputHeight(inputHeightRef.current);
|
||||
}, [setBoundedInputHeight]);
|
||||
|
||||
useComposerHeightMirror({
|
||||
const measureComposerHeight = useComposerHeightMirror({
|
||||
value,
|
||||
textareaRef: webTextareaRef,
|
||||
minHeight: MIN_INPUT_HEIGHT,
|
||||
@@ -1689,10 +1689,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
const handleInputChange = useCallback(
|
||||
(nextValue: string) => {
|
||||
valueRef.current = nextValue;
|
||||
measureComposerHeight(nextValue);
|
||||
updateLiveTextPresence(nextValue);
|
||||
onChangeText(nextValue);
|
||||
},
|
||||
[onChangeText, updateLiveTextPresence],
|
||||
[measureComposerHeight, onChangeText, updateLiveTextPresence],
|
||||
);
|
||||
|
||||
const handleInputFocus = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user