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 { useComposerKeyboardScope } from "@/composer/keyboard-scope";
|
||||||
import { RenderProfile } from "@/utils/render-profiler";
|
import { RenderProfile } from "@/utils/render-profiler";
|
||||||
import { useComposerHeightMirror } from "./height-mirror";
|
import { useComposerHeightMirror } from "./height-mirror";
|
||||||
|
import { resolveComposerInputHeightStyle, shouldScrollComposerInput } from "./height-style";
|
||||||
import { resolveComposerInputMode, type ComposerInputMode } from "@/composer/input-mode";
|
import { resolveComposerInputMode, type ComposerInputMode } from "@/composer/input-mode";
|
||||||
import type { NativePastedFile } from "@/composer/native-pasted-image";
|
import type { NativePastedFile } from "@/composer/native-pasted-image";
|
||||||
import {
|
import {
|
||||||
@@ -990,20 +991,6 @@ function resolveMaxInputHeight(windowHeight: number): number {
|
|||||||
return Math.max(DEFAULT_MAX_INPUT_HEIGHT, Math.floor(windowHeight * MAX_INPUT_VIEWPORT_RATIO));
|
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 {
|
function isTextAreaLike(v: unknown): v is TextAreaHandle {
|
||||||
return typeof v === "object" && v !== null && "scrollHeight" in v;
|
return typeof v === "object" && v !== null && "scrollHeight" in v;
|
||||||
}
|
}
|
||||||
@@ -1764,7 +1751,12 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
() => [
|
() => [
|
||||||
styles.textInput,
|
styles.textInput,
|
||||||
mode.isMonospace && styles.textInputMonospace,
|
mode.isMonospace && styles.textInputMonospace,
|
||||||
computeTextInputHeightStyle(inputHeight, maxInputHeight),
|
resolveComposerInputHeightStyle({
|
||||||
|
inputHeight,
|
||||||
|
minInputHeight: MIN_INPUT_HEIGHT,
|
||||||
|
maxInputHeight,
|
||||||
|
applyMeasuredHeight: isWeb,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
[inputHeight, maxInputHeight, mode.isMonospace],
|
[inputHeight, maxInputHeight, mode.isMonospace],
|
||||||
);
|
);
|
||||||
@@ -1838,7 +1830,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
onFocus={handleInputFocus}
|
onFocus={handleInputFocus}
|
||||||
onBlur={handleInputBlur}
|
onBlur={handleInputBlur}
|
||||||
editable={!isDictating && !isRealtimeVoiceForCurrentAgent && !disabled}
|
editable={!isDictating && !isRealtimeVoiceForCurrentAgent && !disabled}
|
||||||
scrollEnabled={isWeb ? inputHeight >= maxInputHeight : true}
|
scrollEnabled={shouldScrollComposerInput({ inputHeight, maxInputHeight })}
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
onContentSizeChange={handleContentSizeChange}
|
onContentSizeChange={handleContentSizeChange}
|
||||||
onKeyPress={shouldHandleWebKeyPress ? handleDesktopKeyPress : undefined}
|
onKeyPress={shouldHandleWebKeyPress ? handleDesktopKeyPress : undefined}
|
||||||
|
|||||||
Reference in New Issue
Block a user