99d239c8bf
复用了画布agent的输入框: * 高度自适应 * 滑动条样式变为浅色和位置不再出界 before  after  Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/130 Co-authored-by: 王德宇 <kvtodev@outlook.com> Co-committed-by: 王德宇 <kvtodev@outlook.com>
131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
import {
|
|
type InputEventHandler,
|
|
type TextareaHTMLAttributes,
|
|
useLayoutEffect,
|
|
useRef,
|
|
} from 'react';
|
|
|
|
const DEFAULT_MIN_HEIGHT_PX = 40;
|
|
const DEFAULT_MAX_HEIGHT_PX = 128;
|
|
|
|
function supportsNativeFieldSizing() {
|
|
return (
|
|
typeof CSS !== 'undefined' &&
|
|
typeof CSS.supports === 'function' &&
|
|
CSS.supports('field-sizing', 'content')
|
|
);
|
|
}
|
|
|
|
function parseComputedPixelValue(value: string, fallback: number) {
|
|
const parsedValue = Number.parseFloat(value);
|
|
return Number.isFinite(parsedValue) ? parsedValue : fallback;
|
|
}
|
|
|
|
function resizeFallbackTextArea(textarea: HTMLTextAreaElement) {
|
|
textarea.style.height = 'auto';
|
|
|
|
const computedStyle = window.getComputedStyle(textarea);
|
|
const minHeight = parseComputedPixelValue(
|
|
computedStyle.minHeight,
|
|
DEFAULT_MIN_HEIGHT_PX,
|
|
);
|
|
const maxHeight = Math.max(
|
|
minHeight,
|
|
parseComputedPixelValue(computedStyle.maxHeight, DEFAULT_MAX_HEIGHT_PX),
|
|
);
|
|
const contentHeight = textarea.scrollHeight;
|
|
const borderHeight = Math.max(
|
|
0,
|
|
textarea.offsetHeight - textarea.clientHeight,
|
|
);
|
|
const isBorderBox = computedStyle.boxSizing === 'border-box';
|
|
const paddingHeight =
|
|
parseComputedPixelValue(computedStyle.paddingTop, 0) +
|
|
parseComputedPixelValue(computedStyle.paddingBottom, 0);
|
|
const measuredHeight = isBorderBox
|
|
? contentHeight + borderHeight
|
|
: Math.max(0, contentHeight - paddingHeight);
|
|
const nextHeight = Math.min(
|
|
Math.max(measuredHeight, minHeight),
|
|
maxHeight,
|
|
);
|
|
|
|
textarea.style.height = `${nextHeight}px`;
|
|
textarea.style.overflowY = measuredHeight > maxHeight ? 'auto' : 'hidden';
|
|
}
|
|
|
|
export function AutoGrowTextArea({
|
|
className,
|
|
onInput,
|
|
rows = 1,
|
|
value,
|
|
...textareaProps
|
|
}: TextareaHTMLAttributes<HTMLTextAreaElement>) {
|
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
useLayoutEffect(() => {
|
|
const textarea = textareaRef.current;
|
|
if (!textarea || supportsNativeFieldSizing()) {
|
|
return;
|
|
}
|
|
resizeFallbackTextArea(textarea);
|
|
}, [value]);
|
|
|
|
useLayoutEffect(() => {
|
|
const textarea = textareaRef.current;
|
|
if (!textarea || supportsNativeFieldSizing()) {
|
|
return undefined;
|
|
}
|
|
|
|
let previousWidth = textarea.getBoundingClientRect().width;
|
|
const resizeWhenWidthChanges = (nextWidth: number) => {
|
|
if (Math.abs(nextWidth - previousWidth) < 0.5) {
|
|
return;
|
|
}
|
|
previousWidth = nextWidth;
|
|
resizeFallbackTextArea(textarea);
|
|
};
|
|
|
|
if (typeof ResizeObserver !== 'undefined') {
|
|
const observer = new ResizeObserver((entries) => {
|
|
const entry = entries[0];
|
|
if (entry) {
|
|
resizeWhenWidthChanges(entry.contentRect.width);
|
|
}
|
|
});
|
|
observer.observe(textarea);
|
|
return () => observer.disconnect();
|
|
}
|
|
|
|
const handleWindowResize = () => {
|
|
resizeWhenWidthChanges(textarea.getBoundingClientRect().width);
|
|
};
|
|
window.addEventListener('resize', handleWindowResize);
|
|
return () => window.removeEventListener('resize', handleWindowResize);
|
|
}, []);
|
|
|
|
const resolvedClassName = [
|
|
'auto-grow-text-area max-h-32 min-h-10 resize-none overflow-x-hidden overflow-y-auto overscroll-contain [field-sizing:content] disabled:cursor-not-allowed disabled:opacity-60',
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
const handleInput: InputEventHandler<HTMLTextAreaElement> = (event) => {
|
|
if (!supportsNativeFieldSizing()) {
|
|
resizeFallbackTextArea(event.currentTarget);
|
|
}
|
|
onInput?.(event);
|
|
};
|
|
|
|
return (
|
|
<textarea
|
|
{...textareaProps}
|
|
ref={textareaRef}
|
|
className={resolvedClassName}
|
|
onInput={handleInput}
|
|
rows={rows}
|
|
value={value}
|
|
/>
|
|
);
|
|
}
|