131 lines
3.7 KiB
TypeScript
131 lines
3.7 KiB
TypeScript
import {
|
|
FC,
|
|
TextareaHTMLAttributes,
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
} from "react";
|
|
import { FMCompDef } from "../../../../../utils/types/meta-fn";
|
|
import { useGlobal, useLocal } from "web-utils";
|
|
import { EDGlobal } from "../../../logic/ed-global";
|
|
import { Tooltip } from "../../../../../utils/ui/tooltip";
|
|
|
|
export const EdPropInstanceText: FC<{
|
|
name: string;
|
|
mprop: FMCompDef;
|
|
}> = ({ name, mprop }) => {
|
|
const p = useGlobal(EDGlobal, "EDITOR");
|
|
|
|
const local = useLocal({
|
|
value: "",
|
|
codeEditing: false,
|
|
timeout: null as any,
|
|
});
|
|
|
|
const val = mprop.get("value");
|
|
const valBuilt = mprop.get("valueBuilt");
|
|
|
|
useEffect(() => {
|
|
if (val) {
|
|
try {
|
|
eval(`local.value = ${valBuilt}`);
|
|
} catch (e) {}
|
|
} else {
|
|
local.value = "";
|
|
}
|
|
local.render();
|
|
}, [val, valBuilt]);
|
|
|
|
const label = (
|
|
<div className="pl-1 w-[70px] overflow-hidden text-ellipsis whitespace-nowrap">
|
|
{name}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="flex items-center">
|
|
{name.length > 8 ? (
|
|
<Tooltip content={name} placement="left" delay={100}>
|
|
{label}
|
|
</Tooltip>
|
|
) : (
|
|
label
|
|
)}
|
|
<AutoHeightTextarea
|
|
className="flex-1 outline-none border-l p-1 ml-1 overflow-hidden focus:bg-blue-50"
|
|
value={local.value}
|
|
spellCheck={false}
|
|
onChange={(e) => {
|
|
local.value = e.currentTarget.value;
|
|
local.render();
|
|
clearTimeout(local.timeout);
|
|
local.timeout = setTimeout(() => {
|
|
mprop.doc?.transact(() => {
|
|
mprop.set("value", `\`${local.value}\``);
|
|
mprop.set("valueBuilt", `\`${local.value}\``);
|
|
});
|
|
}, 1000);
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export function AutoHeightTextarea({
|
|
minRows = 1,
|
|
...props
|
|
}: TextareaHTMLAttributes<HTMLTextAreaElement> & { minRows?: number }) {
|
|
const ref = useRef<HTMLTextAreaElement>(null);
|
|
|
|
const calculateAndSetHeight = useCallback(() => {
|
|
if (!ref.current) {
|
|
return;
|
|
}
|
|
const {
|
|
borderBottomWidth,
|
|
borderTopWidth,
|
|
boxSizing,
|
|
lineHeight,
|
|
paddingBottom,
|
|
paddingTop,
|
|
} = window.getComputedStyle(ref.current);
|
|
ref.current.style.height = lineHeight; // set height temporarily to a single row to obtain scrollHeight, disregarding empty space after text (otherwise, scrollHeight would be equal to the height of the element) - this solves auto-shrinking of the textarea (it's not needed for auto-growing it)
|
|
const { scrollHeight } = ref.current; // scrollHeight = content height + padding top + padding bottom
|
|
|
|
if (boxSizing === "border-box") {
|
|
const minHeight =
|
|
parseFloat(lineHeight) * minRows +
|
|
parseFloat(paddingTop) +
|
|
parseFloat(paddingBottom) +
|
|
parseFloat(borderTopWidth) +
|
|
parseFloat(borderBottomWidth);
|
|
const allTextHeight =
|
|
scrollHeight +
|
|
parseFloat(borderTopWidth) +
|
|
parseFloat(borderBottomWidth);
|
|
ref.current.style.height = `${Math.max(minHeight, allTextHeight)}px`;
|
|
} else if (boxSizing === "content-box") {
|
|
const minHeight = parseFloat(lineHeight) * minRows;
|
|
const allTextHeight =
|
|
scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom);
|
|
ref.current.style.height = `${Math.max(minHeight, allTextHeight)}px`;
|
|
} else {
|
|
console.error("Unknown box-sizing value.");
|
|
}
|
|
}, [minRows]);
|
|
|
|
useEffect(() => {
|
|
calculateAndSetHeight();
|
|
}, [calculateAndSetHeight]);
|
|
|
|
const handleChange: React.ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
|
calculateAndSetHeight();
|
|
if (props.onChange) {
|
|
props.onChange(e);
|
|
}
|
|
};
|
|
calculateAndSetHeight();
|
|
|
|
return <textarea {...props} onChange={handleChange} ref={ref} />;
|
|
}
|