All files / src/canvas Signature.tsx

50% Statements 26/52
20.83% Branches 5/24
37.5% Functions 3/8
53.06% Lines 26/49

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 97 98 99 100 101 102 103 104 105 106              1x             1x                   2x 2x 2x 2x 2x 2x 2x 2x 2x 2x                           2x                             2x                     2x             2x 1x 1x   1x 1x 1x 1x 1x 1x 1x     2x                        
import React, { useEffect, useRef, useId, forwardRef, useImperativeHandle } from 'react';
import { getBoundingClientRect, getClinetXY, useEvent } from '../utils';
 
import { SignatureCanvasRef, SignatureProps } from '.';
import { useDispatch } from '../store';
import { useOptionDispatch } from '../options';
 
export const defaultStyle: React.CSSProperties = {
  '--w-signature-background': '#fff',
  touchAction: 'none',
  position: 'relative',
  backgroundColor: 'var(--w-signature-background)',
} as React.CSSProperties;
 
export const Signature = forwardRef<SignatureCanvasRef, SignatureProps>((props, ref) => {
  const {
    className,
    prefixCls = 'w-signature',
    style,
    readonly = false,
    onPointer,
    options,
    children,
    ...others
  } = props;
  const cls = [className, prefixCls].filter(Boolean).join(' ');
  const $canvas = useRef<HTMLCanvasElement>(null);
  const $path = useRef<SVGPathElement>();
  const pointsRef = useRef<number[][]>();
  const pointCount = useRef<number>(0);
  const pointId = useId();
  const dispatch = useDispatch();
  const dispatchOption = useOptionDispatch();
  useImperativeHandle<SignatureCanvasRef, SignatureCanvasRef>(
    ref,
    () => ({
      canvas: $canvas.current,
      dispatch,
      clear: () => {
        dispatch({});
        const ctx = $canvas.current?.getContext('2d');
        ctx?.clearRect(0, 0, $canvas.current?.width || 0, $canvas.current?.height || 0);
      },
    }),
    [$canvas.current, dispatch],
  );
 
  const handlePointerDown = useEvent((e: React.PointerEvent<HTMLCanvasElement>) => {
    if (readonly) return;
    pointCount.current += 1;
    const { offsetY, offsetX } = getBoundingClientRect($canvas.current);
    const clientX = e.clientX || e.nativeEvent.clientX;
    const clientY = e.clientY || e.nativeEvent.clientY;
    pointsRef.current = [[clientX - offsetX, clientY - offsetY]];
    const pathElm = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    $path.current = pathElm;
    $canvas.current!.appendChild(pathElm);
    dispatch({
      [pointId + pointCount.current]: pointsRef.current,
    });
  });
 
  const handlePointerMove = useEvent((e: PointerEvent) => {
    if ($path.current) {
      const { offsetY, offsetX } = getBoundingClientRect($canvas.current);
      const { clientX, clientY } = getClinetXY(e);
      pointsRef.current = [...pointsRef.current!, [clientX - offsetX, clientY - offsetY]];
      dispatch({
        [pointId + pointCount.current]: pointsRef.current,
      });
    }
  });
 
  const handlePointerUp = useEvent(() => {
    let result = pointsRef.current || [];
    onPointer && props.onPointer!(result);
    $path.current = undefined;
    pointsRef.current = undefined;
  });
 
  useEffect(() => {
    Eif ($canvas.current) {
      dispatchOption({ container: $canvas.current });
    }
    Iif (readonly) return;
    document.addEventListener('pointermove', handlePointerMove);
    document.addEventListener('pointerup', handlePointerUp);
    return () => {
      Iif (readonly) return;
      document.removeEventListener('pointermove', handlePointerMove);
      document.removeEventListener('pointerup', handlePointerUp);
    };
  }, []);
  return (
    <canvas
      {...others}
      ref={$canvas}
      className={cls}
      onPointerDown={handlePointerDown}
      style={{ ...defaultStyle, ...style }}
    >
      {children}
    </canvas>
  );
});