Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(EditableTypography): improve performance #2701

Merged
merged 5 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`);
talkor marked this conversation as resolved.
Show resolved Hide resolved

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
Loading