From 453e6deb97f6c613c237ee72c4f95b23e2c7f424 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Tue, 23 Jul 2024 14:55:17 +0200 Subject: [PATCH] fix: Dashboard editable title weird behavior when adding spaces (#29667) --- .../components/DynamicEditableTitle/index.tsx | 267 +++++++++--------- 1 file changed, 139 insertions(+), 128 deletions(-) diff --git a/superset-frontend/src/components/DynamicEditableTitle/index.tsx b/superset-frontend/src/components/DynamicEditableTitle/index.tsx index 6f2d6333a9f70..dd439194383ad 100644 --- a/superset-frontend/src/components/DynamicEditableTitle/index.tsx +++ b/superset-frontend/src/components/DynamicEditableTitle/index.tsx @@ -20,6 +20,7 @@ import { ChangeEvent, KeyboardEvent, + memo, useCallback, useEffect, useLayoutEffect, @@ -72,144 +73,154 @@ const titleStyles = (theme: SupersetTheme) => css` position: absolute; left: -9999px; display: inline-block; + white-space: pre; } `; -export const DynamicEditableTitle = ({ - title, - placeholder, - onSave, - canEdit, - label, -}: DynamicEditableTitleProps) => { - const [isEditing, setIsEditing] = useState(false); - const [currentTitle, setCurrentTitle] = useState(title || ''); - const contentRef = useRef(null); - const [showTooltip, setShowTooltip] = useState(false); - - const { width: inputWidth, ref: sizerRef } = useResizeDetector(); - const { width: containerWidth, ref: containerRef } = useResizeDetector({ - refreshMode: 'debounce', - }); - - useEffect(() => { - setCurrentTitle(title); - }, [title]); - - useEffect(() => { - if (isEditing && contentRef?.current) { - contentRef.current.focus(); - // move cursor and scroll to the end - if (contentRef.current.setSelectionRange) { - const { length } = contentRef.current.value; - contentRef.current.setSelectionRange(length, length); - contentRef.current.scrollLeft = contentRef.current.scrollWidth; +export const DynamicEditableTitle = memo( + ({ + title, + placeholder, + onSave, + canEdit, + label, + }: DynamicEditableTitleProps) => { + const [isEditing, setIsEditing] = useState(false); + const [currentTitle, setCurrentTitle] = useState(title || ''); + const contentRef = useRef(null); + const [showTooltip, setShowTooltip] = useState(false); + + const { width: inputWidth, ref: sizerRef } = useResizeDetector(); + const { width: containerWidth, ref: containerRef } = useResizeDetector({ + refreshMode: 'debounce', + }); + + useEffect(() => { + setCurrentTitle(title); + }, [title]); + + useEffect(() => { + if (isEditing && contentRef?.current) { + contentRef.current.focus(); + // move cursor and scroll to the end + if (contentRef.current.setSelectionRange) { + const { length } = contentRef.current.value; + contentRef.current.setSelectionRange(length, length); + contentRef.current.scrollLeft = contentRef.current.scrollWidth; + } } - } - }, [isEditing]); - - // a trick to make the input grow when user types text - // we make additional span component, place it somewhere out of view and copy input - // then we can measure the width of that span to resize the input element - useLayoutEffect(() => { - if (sizerRef?.current) { - sizerRef.current.textContent = currentTitle || placeholder; - } - }, [currentTitle, placeholder, sizerRef]); - - useEffect(() => { - if ( - contentRef.current && - contentRef.current.scrollWidth > contentRef.current.clientWidth - ) { - setShowTooltip(true); - } else { - setShowTooltip(false); - } - }, [inputWidth, containerWidth]); - - const handleClick = useCallback(() => { - if (!canEdit || isEditing) { - return; - } - setIsEditing(true); - }, [canEdit, isEditing]); - - const handleBlur = useCallback(() => { - if (!canEdit) { - return; - } - const formattedTitle = currentTitle.trim(); - setCurrentTitle(formattedTitle); - if (title !== formattedTitle) { - onSave(formattedTitle); - } - setIsEditing(false); - }, [canEdit, currentTitle, onSave, title]); + }, [isEditing]); + + // a trick to make the input grow when user types text + // we make additional span component, place it somewhere out of view and copy input + // then we can measure the width of that span to resize the input element + useLayoutEffect(() => { + if (sizerRef?.current) { + sizerRef.current.textContent = currentTitle || placeholder; + } + }, [currentTitle, placeholder, sizerRef]); + + useEffect(() => { + if ( + contentRef.current && + contentRef.current.scrollWidth > contentRef.current.clientWidth + ) { + setShowTooltip(true); + } else { + setShowTooltip(false); + } + }, [inputWidth, containerWidth]); - const handleChange = useCallback( - (ev: ChangeEvent) => { - if (!canEdit || !isEditing) { + const handleClick = useCallback(() => { + if (!canEdit || isEditing) { return; } - setCurrentTitle(ev.target.value); - }, - [canEdit, isEditing], - ); + setIsEditing(true); + }, [canEdit, isEditing]); - const handleKeyPress = useCallback( - (ev: KeyboardEvent) => { + const handleBlur = useCallback(() => { if (!canEdit) { return; } - if (ev.key === 'Enter') { - ev.preventDefault(); - contentRef.current?.blur(); + const formattedTitle = currentTitle.trim(); + setCurrentTitle(formattedTitle); + if (title !== formattedTitle) { + onSave(formattedTitle); } - }, - [canEdit], - ); - - return ( -
- - {canEdit ? ( - 0 && - css` - width: ${inputWidth + 1}px; + setIsEditing(false); + }, [canEdit, currentTitle, onSave, title]); + + const handleChange = useCallback( + (ev: ChangeEvent) => { + if (!canEdit || !isEditing) { + return; + } + setCurrentTitle(ev.target.value); + }, + [canEdit, isEditing], + ); + + const handleKeyPress = useCallback( + (ev: KeyboardEvent) => { + if (!canEdit) { + return; + } + if (ev.key === 'Enter') { + ev.preventDefault(); + contentRef.current?.blur(); + } + }, + [canEdit], + ); + + return ( +
+ + {canEdit ? ( + 0 && + css` + width: ${inputWidth + 1}px; + `} `} - `} - /> - ) : ( - - {currentTitle} - - )} - - -
- ); -}; + /> + ) : ( + + {currentTitle} + + )} +
+ +
+ ); + }, +);