fix(app): restore native composer auto-growth (#3681)
Native multiline inputs were always scrollable, which kept iOS content-size measurement pinned to one line. Keep native sizing intrinsic below the cap and enable internal scrolling only after the composer reaches it.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveComposerInputHeightStyle, shouldScrollComposerInput } from "./height-style";
|
||||
|
||||
describe("composer input height style", () => {
|
||||
it("applies the measured height alongside its bounds", () => {
|
||||
expect(
|
||||
resolveComposerInputHeightStyle({
|
||||
inputHeight: 96,
|
||||
minInputHeight: 30,
|
||||
maxInputHeight: 240,
|
||||
applyMeasuredHeight: true,
|
||||
}),
|
||||
).toEqual({
|
||||
height: 96,
|
||||
minHeight: 30,
|
||||
maxHeight: 240,
|
||||
});
|
||||
});
|
||||
|
||||
it("lets a native multiline input expose its intrinsic content height", () => {
|
||||
expect(
|
||||
resolveComposerInputHeightStyle({
|
||||
inputHeight: 96,
|
||||
minInputHeight: 30,
|
||||
maxInputHeight: 240,
|
||||
applyMeasuredHeight: false,
|
||||
}),
|
||||
).toEqual({
|
||||
minHeight: 30,
|
||||
maxHeight: 240,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps native measurement unconstrained until the composer reaches its cap", () => {
|
||||
expect(shouldScrollComposerInput({ inputHeight: 30, maxInputHeight: 240 })).toBe(false);
|
||||
expect(shouldScrollComposerInput({ inputHeight: 240, maxInputHeight: 240 })).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
interface ComposerInputHeightStyleInput {
|
||||
inputHeight: number;
|
||||
minInputHeight: number;
|
||||
maxInputHeight: number;
|
||||
applyMeasuredHeight: boolean;
|
||||
}
|
||||
|
||||
export function resolveComposerInputHeightStyle(input: ComposerInputHeightStyleInput) {
|
||||
// Native must remain intrinsically sized below the cap so
|
||||
// onContentSizeChange can observe growth. Web gets its height from the DOM
|
||||
// mirror and therefore needs that measurement applied explicitly.
|
||||
return {
|
||||
...(input.applyMeasuredHeight ? { height: input.inputHeight } : {}),
|
||||
minHeight: input.minInputHeight,
|
||||
maxHeight: input.maxInputHeight,
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldScrollComposerInput(input: {
|
||||
inputHeight: number;
|
||||
maxInputHeight: number;
|
||||
}): boolean {
|
||||
return input.inputHeight >= input.maxInputHeight;
|
||||
}
|
||||
@@ -56,6 +56,7 @@ import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { useComposerKeyboardScope } from "@/composer/keyboard-scope";
|
||||
import { RenderProfile } from "@/utils/render-profiler";
|
||||
import { useComposerHeightMirror } from "./height-mirror";
|
||||
import { resolveComposerInputHeightStyle, shouldScrollComposerInput } from "./height-style";
|
||||
import { resolveComposerInputMode, type ComposerInputMode } from "@/composer/input-mode";
|
||||
import type { NativePastedFile } from "@/composer/native-pasted-image";
|
||||
import {
|
||||
@@ -990,20 +991,6 @@ function resolveMaxInputHeight(windowHeight: number): number {
|
||||
return Math.max(DEFAULT_MAX_INPUT_HEIGHT, Math.floor(windowHeight * MAX_INPUT_VIEWPORT_RATIO));
|
||||
}
|
||||
|
||||
function computeTextInputHeightStyle(inputHeight: number, maxInputHeight: number) {
|
||||
if (isWeb) {
|
||||
return {
|
||||
height: inputHeight,
|
||||
minHeight: MIN_INPUT_HEIGHT,
|
||||
maxHeight: maxInputHeight,
|
||||
};
|
||||
}
|
||||
return {
|
||||
minHeight: MIN_INPUT_HEIGHT,
|
||||
maxHeight: maxInputHeight,
|
||||
};
|
||||
}
|
||||
|
||||
function isTextAreaLike(v: unknown): v is TextAreaHandle {
|
||||
return typeof v === "object" && v !== null && "scrollHeight" in v;
|
||||
}
|
||||
@@ -1764,7 +1751,12 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
() => [
|
||||
styles.textInput,
|
||||
mode.isMonospace && styles.textInputMonospace,
|
||||
computeTextInputHeightStyle(inputHeight, maxInputHeight),
|
||||
resolveComposerInputHeightStyle({
|
||||
inputHeight,
|
||||
minInputHeight: MIN_INPUT_HEIGHT,
|
||||
maxInputHeight,
|
||||
applyMeasuredHeight: isWeb,
|
||||
}),
|
||||
],
|
||||
[inputHeight, maxInputHeight, mode.isMonospace],
|
||||
);
|
||||
@@ -1838,7 +1830,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
editable={!isDictating && !isRealtimeVoiceForCurrentAgent && !disabled}
|
||||
scrollEnabled={isWeb ? inputHeight >= maxInputHeight : true}
|
||||
scrollEnabled={shouldScrollComposerInput({ inputHeight, maxInputHeight })}
|
||||
autoFocus={false}
|
||||
onContentSizeChange={handleContentSizeChange}
|
||||
onKeyPress={shouldHandleWebKeyPress ? handleDesktopKeyPress : undefined}
|
||||
|
||||
Reference in New Issue
Block a user