All files / drag-event-interactive/src index.tsx

95.55% Statements 43/45
91.3% Branches 21/23
100% Functions 8/8
100% Lines 40/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96                      1x 10x 10x 10x 10x 10x 10x       10x 4x 3x 3x   10x   3x 3x   3x 3x 1x 1x     2x         10x 10x   18x 3x 3x   15x 15x 15x 15x           10x 9x 9x 9x       10x   4x 4x 3x 3x 3x         10x                               1x      
import React, { useRef, useState, useCallback, useEffect } from 'react';
import { isTouch, preventDefaultMove, getRelativePosition, Interaction, useEventCallback } from './utils';
 
export * from './utils';
 
export interface InteractiveProps extends React.HTMLAttributes<HTMLDivElement> {
  prefixCls?: string;
  onMove?: (interaction: Interaction, event: MouseEvent | TouchEvent) => void;
  onDown?: (offset: Interaction, event: MouseEvent | TouchEvent) => void;
}
 
const Interactive = React.forwardRef<HTMLDivElement, InteractiveProps>((props, ref) => {
  const { prefixCls = 'w-color-interactive', className, onMove, onDown, ...reset } = props;
  const container = useRef<HTMLDivElement>(null);
  const hasTouched = useRef(false);
  const [isDragging, setDragging] = useState(false);
  const onMoveCallback = useEventCallback<Interaction, MouseEvent | TouchEvent>(onMove);
  const onKeyCallback = useEventCallback<Interaction, MouseEvent | TouchEvent>(onDown);
 
  // Prevent mobile browsers from handling mouse events (conflicting with touch ones).
  // If we detected a touch interaction before, we prefer reacting to touch events only.
  const isValid = (event: MouseEvent | TouchEvent): boolean => {
    if (hasTouched.current && !isTouch(event)) return false;
    hasTouched.current = isTouch(event);
    return true;
  };
  const handleMove = useCallback(
    (event: MouseEvent | TouchEvent) => {
      preventDefaultMove(event);
      Iif (!container.current) return;
 
      const isDown = isTouch(event) ? event.touches.length > 0 : event.buttons > 0;
      if (!isDown) {
        setDragging(false);
        return;
      }
 
      onMoveCallback?.(getRelativePosition(container.current, event), event);
    },
    [onMoveCallback],
  );
 
  const handleMoveEnd = useCallback(() => setDragging(false), []);
  const toggleDocumentEvents = useCallback(
    (state: boolean) => {
      if (state) {
        window.addEventListener(hasTouched.current ? 'touchmove' : 'mousemove', handleMove);
        window.addEventListener(hasTouched.current ? 'touchend' : 'mouseup', handleMoveEnd);
      } else {
        window.removeEventListener('mousemove', handleMove);
        window.removeEventListener('mouseup', handleMoveEnd);
        window.removeEventListener('touchmove', handleMove);
        window.removeEventListener('touchend', handleMoveEnd);
      }
    },
    [handleMove, handleMoveEnd],
  );
 
  useEffect(() => {
    toggleDocumentEvents(isDragging);
    return () => {
      toggleDocumentEvents(false);
    };
  }, [isDragging, handleMove, handleMoveEnd, toggleDocumentEvents]);
 
  const handleMoveStart = useCallback(
    (event: React.MouseEvent | React.TouchEvent) => {
      preventDefaultMove(event.nativeEvent);
      if (!isValid(event.nativeEvent)) return;
      Iif (!container.current) return;
      onKeyCallback?.(getRelativePosition(container.current, event.nativeEvent), event.nativeEvent);
      setDragging(true);
    },
    [onKeyCallback],
  );
 
  return (
    <div
      {...reset}
      className={[prefixCls, className || ''].filter(Boolean).join(' ')}
      style={{
        ...reset.style,
        touchAction: 'none',
      }}
      ref={container}
      tabIndex={0}
      onMouseDown={handleMoveStart}
      onTouchStart={handleMoveStart}
    />
  );
});
 
Interactive.displayName = 'Interactive';
 
export default Interactive;