diff --git a/src/internal/components/drag-handle-wrapper/__tests__/drag-handle-wrapper.test.tsx b/src/internal/components/drag-handle-wrapper/__tests__/drag-handle-wrapper.test.tsx index 590a048b5f..6c0f257ac8 100644 --- a/src/internal/components/drag-handle-wrapper/__tests__/drag-handle-wrapper.test.tsx +++ b/src/internal/components/drag-handle-wrapper/__tests__/drag-handle-wrapper.test.tsx @@ -53,9 +53,15 @@ function expectDirectionButtonToBeVisible(direction: Direction) { ).toBe(true); } -function renderDragHandle(props: Omit) { +function renderDragHandle(props: Partial>) { + const mergedProps: Omit = { + directions: {}, + hideButtonsOnDrag: false, + clickDragThreshold: 3, + ...props, + }; const { container } = render( - + @@ -343,7 +349,7 @@ test('shows direction buttons when dragged 2 pixels', () => { expect(getDirectionButton('block-start')).toBeVisible(); }); -test("doesn't show direction buttons when dragged more than 3 pixels", () => { +test("doesn't show direction buttons when dragged more than 3 pixels (default threshold)", () => { const { dragHandle } = renderDragHandle({ directions: { 'block-start': 'active' }, tooltipText: 'Click me!', @@ -355,6 +361,49 @@ test("doesn't show direction buttons when dragged more than 3 pixels", () => { expectDirectionButtonToBeHidden('block-start'); }); +test('shows direction buttons when dragged less than custom clickDragThreshold', () => { + const { dragHandle } = renderDragHandle({ + directions: { 'block-start': 'active' }, + tooltipText: 'Click me!', + clickDragThreshold: 10, + }); + + fireEvent.pointerDown(dragHandle, { clientX: 50, clientY: 50 }); + fireEvent.pointerMove(dragHandle, { clientX: 55, clientY: 55 }); + fireEvent.pointerUp(dragHandle); + expectDirectionButtonToBeVisible('block-start'); +}); + +describe('hideButtonsOnDrag property', () => { + test('hides direction buttons when dragging with hideButtonsOnDrag=true', () => { + const { dragHandle, showButtons } = renderDragHandle({ + directions: { 'block-start': 'active' }, + tooltipText: 'Click me!', + hideButtonsOnDrag: true, + }); + + showButtons(); + expectDirectionButtonToBeVisible('block-start'); + fireEvent.pointerDown(dragHandle, { clientX: 50, clientY: 50 }); + fireEvent.pointerMove(dragHandle, { clientX: 55, clientY: 55 }); + expectDirectionButtonToBeHidden('block-start'); + }); + + test('keeps direction buttons visible when dragging with hideButtonsOnDrag=false (default)', () => { + const { dragHandle, showButtons } = renderDragHandle({ + directions: { 'block-start': 'active' }, + tooltipText: 'Click me!', + hideButtonsOnDrag: false, + }); + + showButtons(); + expectDirectionButtonToBeVisible('block-start'); + fireEvent.pointerDown(dragHandle, { clientX: 50, clientY: 50 }); + fireEvent.pointerMove(dragHandle, { clientX: 55, clientY: 55 }); // Move more than default threshold + expectDirectionButtonToBeVisible('block-start'); + }); +}); + test('hides direction buttons on Escape keypress', () => { const { dragHandle, showButtons } = renderDragHandle({ directions: { 'block-start': 'active' }, diff --git a/src/internal/components/drag-handle-wrapper/index.tsx b/src/internal/components/drag-handle-wrapper/index.tsx index 41713e45fa..134a23d86b 100644 --- a/src/internal/components/drag-handle-wrapper/index.tsx +++ b/src/internal/components/drag-handle-wrapper/index.tsx @@ -14,12 +14,6 @@ import PortalOverlay from './portal-overlay'; import styles from './styles.css.js'; -// The amount of distance after pointer down that the cursor is allowed to -// jitter for a subsequent mouseup to still register as a "press" instead of -// a drag. A little allowance is needed for usability reasons, but this number -// isn't set in stone. -const PRESS_DELTA_MAX = 3; - export default function DragHandleWrapper({ directions, tooltipText, @@ -27,6 +21,8 @@ export default function DragHandleWrapper({ onDirectionClick, triggerMode = 'focus', initialShowButtons = false, + hideButtonsOnDrag, + clickDragThreshold, }: DragHandleWrapperProps) { const wrapperRef = useRef(null); const dragHandleRef = useRef(null); @@ -70,25 +66,26 @@ export default function DragHandleWrapper({ useEffect(() => { const controller = new AbortController(); - // See `PRESS_DELTA_MAX` above. We need to differentiate between a "click" - // and a "drag" action. We can say a "click" happens when a "pointerdown" - // is followed by a "pointerup" with no "pointermove" between the two. + // We need to differentiate between a "click" and a "drag" action. + // We can say a "click" happens when a "pointerdown" is followed by + // a "pointerup" with no "pointermove" between the two. // However, it would be a poor usability experience if a "click" isn't // registered because, while pressing my mouse, I moved it by just one // pixel, making it a "drag" instead. So we allow the pointer to move by - // `PRESS_DELTA_MAX` pixels before setting `didPointerDrag` to true. + // `clickDragThreshold` pixels before setting `didPointerDrag` to true. document.addEventListener( 'pointermove', event => { if ( isPointerDown.current && initialPointerPosition.current && - (event.clientX > initialPointerPosition.current.x + PRESS_DELTA_MAX || - event.clientX < initialPointerPosition.current.x - PRESS_DELTA_MAX || - event.clientY > initialPointerPosition.current.y + PRESS_DELTA_MAX || - event.clientY < initialPointerPosition.current.y - PRESS_DELTA_MAX) + (event.clientX > initialPointerPosition.current.x + clickDragThreshold || + event.clientX < initialPointerPosition.current.x - clickDragThreshold || + event.clientY > initialPointerPosition.current.y + clickDragThreshold || + event.clientY < initialPointerPosition.current.y - clickDragThreshold) ) { didPointerDrag.current = true; + hideButtonsOnDrag && setShowButtons(false); } }, { signal: controller.signal } @@ -123,7 +120,7 @@ export default function DragHandleWrapper({ ); return () => controller.abort(); - }, []); + }, [clickDragThreshold, hideButtonsOnDrag]); const onHandlePointerDown: React.PointerEventHandler = event => { // Tooltip behavior: the tooltip should appear on hover, but disappear when diff --git a/src/internal/components/drag-handle-wrapper/interfaces.ts b/src/internal/components/drag-handle-wrapper/interfaces.ts index a0e1024151..35af929565 100644 --- a/src/internal/components/drag-handle-wrapper/interfaces.ts +++ b/src/internal/components/drag-handle-wrapper/interfaces.ts @@ -12,4 +12,6 @@ export interface DragHandleWrapperProps { children: React.ReactNode; triggerMode?: TriggerMode; initialShowButtons?: boolean; + hideButtonsOnDrag: boolean; + clickDragThreshold: number; } diff --git a/src/internal/components/drag-handle/index.tsx b/src/internal/components/drag-handle/index.tsx index 3204f47606..49380890dc 100644 --- a/src/internal/components/drag-handle/index.tsx +++ b/src/internal/components/drag-handle/index.tsx @@ -26,6 +26,8 @@ const InternalDragHandle = forwardRef( onDirectionClick, triggerMode, initialShowButtons, + hideButtonsOnDrag = false, + clickDragThreshold = 3, ...rest }: DragHandleProps, ref: React.Ref @@ -39,6 +41,8 @@ const InternalDragHandle = forwardRef( onDirectionClick={onDirectionClick} triggerMode={triggerMode} initialShowButtons={initialShowButtons} + hideButtonsOnDrag={hideButtonsOnDrag} + clickDragThreshold={clickDragThreshold} > void; triggerMode?: TriggerMode; initialShowButtons?: boolean; + /** + * Hide the UAP buttons when dragging is active. + */ + hideButtonsOnDrag?: boolean; + /** + * Max cursor movement (in pixels) that still counts as a press rather than + * a drag. Small threshold needed for usability. + */ + clickDragThreshold?: number; } export namespace DragHandleProps {