Source: input/Gesture.js

import { usePinch, useGesture, useDrag } from '@use-gesture/react'
import { useSpring, animated } from '@react-spring/web'
import { useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { appActions } from '../../redux';
import { uuid } from '../../_core';

/**
 * Componente contenedor que utiliza gestos de arrastre,
 * para actualizar una variable segun la direccion en la que se arrastró el elemento
 * @name Reactor.Components.Input.Gesture
 * @param {string!} name 
 *    El nombre o identificador del elemento
 * @param {string} [distance = "5"] 
 *    Distancia en pixeles en la que se producirá el evento
 * @param {string} [directions = "lrtb"] 
 *    Direcciones en las que se puede mover el div para generar gestos
 * @param {string?} variable Nombre de la variable que se va a actualizar
 * @param {string?} context Nombre del contexto que contiene la variable
 * @param {"true"|"false"} [storage = "false"] Si es true el dato se guarda en el storage del explorador
 * @param {"true"|"false"} [debug = "false"] Si es true se obtendra una salida en la consola del navegador cuando se produzca un evento
 * @class 
 * @returns {string} [name]-[direction]-[guid]
 * @example 
<div style="width: 200px; display: flex;  align-items: center;  height: 200px;  justify-content: center;">
  <gesture name="id_item_1" variable="accion" context="test_gesture" debug="true" distance="25" directions="lr">
    <div style="background-color: red; width: 30px; height: 30px; border-radius: 5px; margin: 5px"></div>
  </gesture>
  <gesture name="id_item_2" variable="accion" context="test_gesture" debug="true" distance="25" directions="tb">
    <div style="background-color: blue; width: 30px; height: 30px; border-radius: 5px; margin: 5px"></div>
  </gesture>
  <gesture name="id_item_3" variable="accion" context="test_gesture" debug="true" distance="25">
    <div style="background-color: green; width: 30px; height: 30px; border-radius: 5px; margin: 5px"></div>
  </gesture>
  <gesture name="id_item_4" variable="accion" context="test_gesture" debug="true" distance="25">
    <div style="background-color: yellow; width: 30px; height: 30px; border-radius: 5px; margin: 5px"></div>
  </gesture>
</div>
*/

export const Gesture = ({name, distance = "5", directions = "lrtb", variable, context, storage = "false", debug = "false", children}) => {

  const dist = useMemo(() => parseInt(distance), [distance]);
  const dispatch = useDispatch();
  const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }));
  const dirs = useMemo(()=>({
    left: directions.includes("l"),
    right: directions.includes("r"),
    top: directions.includes("t"),
    bottom: directions.includes("b")
  }), [directions]);
  
  // Set the drag hook and define component movement based on gesture data
  const bind = useDrag(
    (state) => {
      const { active, direction: [dirX, dirY], distance: [disX, disY], canceled, cancel, down, movement: [mx, my] } = state;
      if(!down){
        cancel();
      }

      if(
        !dirs.left && mx < 0 ||
        !dirs.top && my < 0 ||
        !dirs.right && mx > 0 ||
        !dirs.bottom && my > 0
      ) {
        cancel();
        return;
      }
      const fireEventLeft   = down && !canceled && mx < -dist;
      const fireEventRight  = down && !canceled && mx > dist;
      const fireEventTop    = down && !canceled && my < -dist;
      const fireEventBottom = down && !canceled && my > dist;

      if(fireEventLeft || fireEventRight || fireEventTop || fireEventBottom){
        const event = fireEventLeft 
          ? "left" 
          : (fireEventRight 
            ? "rigth" 
            : (fireEventTop 
              ? "top" 
              : (fireEventBottom 
                ? "bottom" 
                : "")));

        const newValue = `${name}-${event}-${uuid().replaceAll('-', '')}`;
        dispatch(appActions.setVariable(variable, newValue, context, storage === "true"));
        cancel();
        debug === "true" && console.log({mx, my, down, canceled, event: newValue, variable, context});
      }

      api.start({ 
        x: active ? mx : 0, 
        y: active ? my : 0, 
        immediate: active 
      });
      
    },
    {
      axis: 'lock',
      threshold: 20,
      rubberband: 1,
      bounds: { left: -dist, right: dist, top: -dist, bottom: dist }
    }
  )

  return <animated.div {...bind()} style={{ x, y, touchAction: "none"}}>{children}</animated.div>
}