1import { useLayoutEffect, useState } from "react";
2import { observeResize } from "../../lib/observeResize";
3
4export function measureComposerMultilinePrompt(body: HTMLElement): boolean | null {
5 const editor = body.querySelector<HTMLElement>('[data-testid="composer-editor"]');
6 if (!editor || editor.clientWidth === 0) return null;
7
8 const bodyStyle = getComputedStyle(body);
9 const expandedWidth =
10 body.clientWidth -
11 Number.parseFloat(bodyStyle.paddingLeft) -
12 Number.parseFloat(bodyStyle.paddingRight);
13 const lineHeight = Number.parseFloat(getComputedStyle(editor).lineHeight);
14 const range = document.createRange();
15 range.selectNodeContents(editor);
16 const bounds = range.getBoundingClientRect();
17
18 // Measure content, not the editor's minimum height. While resting, the
19 // prompt is unwrapped: compare its width with the expanded row so that
20 // moving the actions inline cannot make collapse and expansion oscillate.
21 return bounds.height > lineHeight + 1 || bounds.width > expandedWidth + 1;
22}
23
24export function useComposerMultilinePrompt(body: HTMLElement | null): boolean {
25 const [isMultiline, setIsMultiline] = useState(false);
26
27 useLayoutEffect(() => {
28 if (!body) return;
29 const measure = () => {
30 const next = measureComposerMultilinePrompt(body);
31 if (next !== null) setIsMultiline(next);
32 };
33 measure();
34 const editor = body.querySelector<HTMLElement>('[data-testid="composer-editor"]');
35 const stopObserving = observeResize(editor ? [body, editor] : body, measure);
36 const mutationObserver = new MutationObserver(measure);
37 mutationObserver.observe(body, { childList: true, characterData: true, subtree: true });
38 return () => {
39 stopObserving();
40 mutationObserver.disconnect();
41 };
42 }, [body]);
43
44 return isMultiline;
45}