Skip to content

Commit

Permalink
fix(EditableTypography): improve performance (#2701)
Browse files Browse the repository at this point in the history
  • Loading branch information
talkor authored Jan 6, 2025
1 parent 01ceab5 commit f7e5514
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export const Multiline = {
type={EditableText.types.TEXT1}
weight={EditableText.weights.NORMAL}
multiline
value={"This is a multiline\nhere's the second line"}
value={`This is a multiline
here's the second line`}
className={styles.editableText}
/>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
overflow: hidden;
position: relative;

.input,.textarea {
.input,
.textarea {
width: var(--input-width);
display: inline-block;
max-width: 100%;
min-width: 64px;
Expand All @@ -28,6 +30,8 @@

.textarea {
resize: none;
overflow: hidden;
height: var(--input-height);
}

.typography {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TooltipProps } from "../Tooltip/Tooltip";
import usePrevious from "../../hooks/usePrevious";
import { TextType, TextWeight } from "../Text/Text.types";
import { HeadingType, HeadingWeight } from "../Heading/Heading.types";
import useIsomorphicLayoutEffect from "../../hooks/ssr/useIsomorphicLayoutEffect";

export interface EditableTypographyImplementationProps {
/** Value of the text */
Expand Down Expand Up @@ -49,6 +50,8 @@ export interface EditableTypographyProps extends VibeComponentProps, EditableTyp
multiline?: boolean;
}

const PADDING_OFFSET = 2;

const EditableTypography: VibeComponent<EditableTypographyProps, HTMLElement> = forwardRef(
(
{
Expand Down Expand Up @@ -79,10 +82,6 @@ const EditableTypography: VibeComponent<EditableTypographyProps, HTMLElement> =

const [isEditing, setIsEditing] = useState(isEditMode || false);
const [inputValue, setInputValue] = useState(value);
const [inputWidth, setInputWidth] = useState(0);
const [inputHeight, setInputHeight] = useState<number | string>(0);
const textareaBorderBoxSizing = useRef(0);
const textareaLineHeight = useRef(0);

const prevValue = usePrevious(value);

Expand Down Expand Up @@ -157,10 +156,6 @@ const EditableTypography: VibeComponent<EditableTypographyProps, HTMLElement> =

function handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
setInputValue(event.target.value);

if (multiline) {
resizeTextarea();
}
}

const toggleKeyboardEditMode = useKeyboardButtonPressedFunc(toggleEditMode);
Expand All @@ -173,34 +168,6 @@ const EditableTypography: VibeComponent<EditableTypographyProps, HTMLElement> =
const textLength = inputElement.value.length;
inputElement.setSelectionRange(textLength, textLength);
}

if (multiline) {
calculateTextareaHeightAttrs();
}
}

/* Dynamically resizes the textarea to fit its content */
function resizeTextarea() {
if (inputRef.current) {
// Temporarily set the height to "auto" to accurately measure the scroll height of the content inside the textarea.
setInputHeight("auto");

requestAnimationFrame(() => {
const textarea = inputRef.current as HTMLTextAreaElement;

if (!textarea) {
return;
}

// Ensure we at least have 1 line
setInputHeight(
Math.max(
textarea.scrollHeight + textareaBorderBoxSizing.current,
textareaLineHeight.current + textareaBorderBoxSizing.current
)
);
});
}
}

function selectAllInputText() {
Expand All @@ -215,41 +182,20 @@ const EditableTypography: VibeComponent<EditableTypographyProps, HTMLElement> =
}
}, [autoSelectTextOnEditMode, isEditing]);

useEffect(() => {
useIsomorphicLayoutEffect(() => {
if (!typographyRef.current) {
return;
}

const { width } = typographyRef.current.getBoundingClientRect();
setInputWidth(width);
}, [inputValue, isEditing]);

/* Calculate the minimual textarea height, taking its applied styles (padding, border width) into consideration
This is done only on focus, so that we don't need to get the computed style every time.
*/
function calculateTextareaHeightAttrs() {
if (multiline && inputRef.current) {
const textarea = inputRef.current as HTMLTextAreaElement;

if (!textarea) {
return;
}

const computedStyle = window.getComputedStyle(textarea);

// Calculate the appropriate height by taking into account the scrollable content inside the textarea,
// as well as the styles applied to it, such as padding and border widths.
const lineHeight = parseFloat(computedStyle.lineHeight) || 16;
const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;
const borderTopWidth = parseFloat(computedStyle.borderTopWidth) || 0;
const borderBottomWidth = parseFloat(computedStyle.borderBottomWidth) || 0;
inputRef?.current?.style.setProperty("--input-width", `${width}px`);

textareaLineHeight.current = lineHeight;
textareaBorderBoxSizing.current = paddingTop + paddingBottom + borderTopWidth + borderBottomWidth;
resizeTextarea();
if (multiline) {
const textareaElement = inputRef?.current as HTMLTextAreaElement;
textareaElement?.style.setProperty("--input-height", "auto");
textareaElement?.style.setProperty("--input-height", `${textareaElement.scrollHeight + PADDING_OFFSET}px`);
}
}
}, [inputValue, isEditing]);

return (
<div
Expand All @@ -273,7 +219,6 @@ const EditableTypography: VibeComponent<EditableTypographyProps, HTMLElement> =
onBlur={handleBlur}
aria-label={ariaLabel}
placeholder={placeholder}
style={{ width: inputWidth, height: inputHeight }}
role="textbox"
rows={1}
/>
Expand All @@ -287,7 +232,6 @@ const EditableTypography: VibeComponent<EditableTypographyProps, HTMLElement> =
onBlur={handleBlur}
aria-label={ariaLabel}
placeholder={placeholder}
style={{ width: inputWidth }}
role="input"
/>
))}
Expand Down

0 comments on commit f7e5514

Please sign in to comment.