From efa90ea1d6615ef2bcfa246c5a96b47b7a55daec Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Fri, 28 Feb 2025 10:33:26 +0530 Subject: [PATCH] feat: Add useActiveDoubleClick hook for improved double-click handling (#39474) --- .../EditableDismissibleTab.tsx | 9 +++- .../EntityExplorer/EntityItem/EntityItem.tsx | 11 ++++- .../design-system/ads/src/__hooks__/index.ts | 1 + .../ads/src/__hooks__/useActiveDoubleClick.ts | 43 +++++++++++++++++++ .../useEditableText/useEditableText.ts | 16 ------- 5 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 app/client/packages/design-system/ads/src/__hooks__/useActiveDoubleClick.ts diff --git a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx index 06b9709ab5..18eba022a6 100644 --- a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx +++ b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx @@ -6,6 +6,7 @@ import { DismissibleTab } from "../../DismissibleTab"; import { EditableEntityName } from "../EditableEntityName"; import type { EditableDismissibleTabProps } from "./EditableDismissibleTab.types"; +import { useActiveDoubleClick } from "../../__hooks__"; export const EditableDismissibleTab = (props: EditableDismissibleTabProps) => { const { @@ -33,7 +34,13 @@ export const EditableDismissibleTab = (props: EditableDismissibleTabProps) => { const isEditing = propIsEditing ?? localIsEditing; const handleEnterEditMode = propOnEnterEditMode ?? localOnEnterEditMode; const handleExitEditMode = propOnExitEditMode ?? localOnExitEditMode; - const handleDoubleClick = isEditable ? handleEnterEditMode : noop; + + const doubleClickOverride = useActiveDoubleClick( + isActive, + handleEnterEditMode, + ); + + const handleDoubleClick = isEditable ? doubleClickOverride : noop; return ( { + const { onDoubleClick, startIcon, ...rest } = props; + + const doubleClickOverride = useActiveDoubleClick( + props.isSelected || false, + onDoubleClick, + ); + const { canEdit, isEditing, @@ -14,8 +22,6 @@ export const EntityItem = (props: EntityItemProps) => { validateName, } = props.nameEditorConfig; - const { startIcon, ...rest } = props; - const inEditMode = canEdit ? isEditing : false; // Use List Item custom title prop to show the editable name @@ -61,6 +67,7 @@ export const EntityItem = (props: EntityItemProps) => { customTitleComponent={customTitle} data-testid={`t--entity-item-${props.title}`} id={"entity-" + props.id} + onDoubleClick={doubleClickOverride} rightControl={rightControl} /> ); diff --git a/app/client/packages/design-system/ads/src/__hooks__/index.ts b/app/client/packages/design-system/ads/src/__hooks__/index.ts index b75011d84c..58e82c1a0a 100644 --- a/app/client/packages/design-system/ads/src/__hooks__/index.ts +++ b/app/client/packages/design-system/ads/src/__hooks__/index.ts @@ -1,2 +1,3 @@ export { useDOMRef } from "./useDomRef"; export { useEditableText } from "./useEditableText"; +export { useActiveDoubleClick } from "./useActiveDoubleClick"; diff --git a/app/client/packages/design-system/ads/src/__hooks__/useActiveDoubleClick.ts b/app/client/packages/design-system/ads/src/__hooks__/useActiveDoubleClick.ts new file mode 100644 index 0000000000..e306943089 --- /dev/null +++ b/app/client/packages/design-system/ads/src/__hooks__/useActiveDoubleClick.ts @@ -0,0 +1,43 @@ +import { noop } from "lodash"; +import { useEffect, useMemo } from "react"; +import { useBoolean } from "usehooks-ts"; + +export function useActiveDoubleClick( + isActive: boolean, + onDoubleClick?: () => void, +) { + const { + setFalse: setCannotDoubleClick, + setTrue: setCanDoubleClick, + value: canDoubleClick, + } = useBoolean(); + + useEffect( + function handleDoubleClickEnableBasedOnSelection() { + let timeoutId: ReturnType; + + if (isActive) { + timeoutId = setTimeout(() => { + setCanDoubleClick(); + }, 200); + } else { + setCannotDoubleClick(); + } + + return () => { + clearTimeout(timeoutId); + }; + }, + [isActive, setCanDoubleClick, setCannotDoubleClick], + ); + + const handleDoubleClick = useMemo(() => { + if (!canDoubleClick || !onDoubleClick) { + return noop; + } + + return onDoubleClick; + }, [canDoubleClick, onDoubleClick]); + + return handleDoubleClick; +} diff --git a/app/client/packages/design-system/ads/src/__hooks__/useEditableText/useEditableText.ts b/app/client/packages/design-system/ads/src/__hooks__/useEditableText/useEditableText.ts index 80b95891d4..dead483aaa 100644 --- a/app/client/packages/design-system/ads/src/__hooks__/useEditableText/useEditableText.ts +++ b/app/client/packages/design-system/ads/src/__hooks__/useEditableText/useEditableText.ts @@ -115,22 +115,6 @@ export function useEditableText( [name, previousName, isEditing], ); - // TODO: This is a temporary fix to focus the input after context retention applies focus to its target - // this is a nasty hack to re-focus the input after context retention applies focus to its target - // this will be addressed in a future task, likely by a focus retention modification - useEffect( - function recaptureFocusInEventOfFocusRetention() { - const input = inputRef.current; - - if (isEditing && input) { - setTimeout(() => { - input.focus(); - }, 200); - } - }, - [isEditing, inputRef], - ); - return [ inputRef, editableName,