mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-05 23:24:05 +08:00
fix(search): adapt input shape and button position to content line count (#17529)
This commit is contained in:
@@ -1,26 +1,67 @@
|
||||
import { useLayoutEffect, type RefObject } from 'react';
|
||||
import { useLayoutEffect, useState, type RefObject } from 'react';
|
||||
|
||||
/**
|
||||
* Auto-resize a textarea to fit its content, clamped to a maximum height.
|
||||
*
|
||||
* useLayoutEffect runs synchronously after DOM mutation but before the
|
||||
* browser paints, so the height adjustment never produces a visible flicker.
|
||||
*
|
||||
* Measurement also re-runs when the element's content-box width changes
|
||||
* (e.g. responsive layout or font-metric changes): soft-wrapping can add or
|
||||
* remove lines without `value` changing, which would otherwise leave
|
||||
* `isMultiLine` stale and misposition controls. A ResizeObserver watches
|
||||
* width only; height-only changes (which we cause ourselves when setting
|
||||
* `el.style.height`) are ignored to avoid feedback loops.
|
||||
*
|
||||
* Returns `isMultiLine` - true when the rendered content spans more than one
|
||||
* line (including soft-wrapped lines), derived from scrollHeight vs. the
|
||||
* computed single-line height.
|
||||
*/
|
||||
export function useAutoResizeTextarea(
|
||||
ref: RefObject<HTMLTextAreaElement | null>,
|
||||
value: string,
|
||||
maxHeight = 160,
|
||||
) {
|
||||
const [isMultiLine, setIsMultiLine] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
el.style.height = 'auto';
|
||||
// scrollHeight excludes borders while height is border-box, so the
|
||||
// content box ends up a couple of pixels short and overflow-y-auto
|
||||
// would show a scrollbar even for a single line. Hide it until the
|
||||
// content actually exceeds the max height.
|
||||
const overflowing = el.scrollHeight > maxHeight;
|
||||
el.style.overflowY = overflowing ? 'auto' : 'hidden';
|
||||
el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
|
||||
|
||||
const measure = () => {
|
||||
el.style.height = 'auto';
|
||||
// scrollHeight excludes borders while height is border-box, so the
|
||||
// content box ends up a couple of pixels short and overflow-y-auto
|
||||
// would show a scrollbar even for a single line. Hide it until the
|
||||
// content actually exceeds the max height.
|
||||
const scrollHeight = el.scrollHeight;
|
||||
const overflowing = scrollHeight > maxHeight;
|
||||
el.style.overflowY = overflowing ? 'auto' : 'hidden';
|
||||
el.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
|
||||
|
||||
const style = getComputedStyle(el);
|
||||
const lineHeight =
|
||||
parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2;
|
||||
const verticalPadding =
|
||||
parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
|
||||
setIsMultiLine(scrollHeight > verticalPadding + lineHeight + 1);
|
||||
};
|
||||
|
||||
measure();
|
||||
|
||||
let lastWidth: number | undefined;
|
||||
const ro = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const width = entry.contentRect.width;
|
||||
if (width === lastWidth) continue;
|
||||
lastWidth = width;
|
||||
measure();
|
||||
}
|
||||
});
|
||||
ro.observe(el);
|
||||
|
||||
return () => ro.disconnect();
|
||||
}, [ref, value, maxHeight]);
|
||||
|
||||
return isMultiLine;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user