Skip to content

Commit

Permalink
Add swipe
Browse files Browse the repository at this point in the history
  • Loading branch information
HillLiu committed Dec 2, 2024
1 parent 4768dc5 commit 37d7614
Show file tree
Hide file tree
Showing 9 changed files with 1,798 additions and 1,743 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
"memoize-one": "*",
"organism-react-ajax": "*",
"organism-react-popup": "*",
"react-atomic-atom": "*",
"react-atomic-molecule": "*",
"reshow-constant": "*",
"reshow-flux": "*",
"reshow-return": "*",
"reshow-runtime": ">=0.17.4",
"reshow-runtime": "*",
"seturl": "*",
"smooth-scroll-to": "*",
"superagent": "8.0.9",
Expand Down
2 changes: 1 addition & 1 deletion packages/reshow-hooks/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.18.0",
"version": "0.18.1",
"name": "reshow-hooks",
"repository": {
"type": "git",
Expand Down
96 changes: 96 additions & 0 deletions packages/reshow-hooks/src/__tests__/useSwipeTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @ts-check
import * as React from "react";
import { expect } from "chai";

import { useSwipe } from "../useSwipe";
import { render, waitFor, act } from "reshow-unit";

/**
* @typedef {import("../useSwipe").DirectionType} DirectionType
*/

describe("test useSwipe", () => {
it("test swipe up", async ()=>{
let actual;
/**
* @param {DirectionType} direction
*/
const callback = (direction) => {
actual = direction
}
const Dom = () => {
const handles = useSwipe({callback});
handles.onMouseDown(/**@type any*/({clientX: 0, clientY: 101}));
handles.onMouseMove(/**@type any*/({clientX: 0, clientY: 0}));
handles.onMouseUp(/**@type any*/({}));
return <div {...handles}/>;
};
render(<Dom />);
await act(()=>{
expect(actual).to.equal("up");
});
});

it("test swipe right", async ()=>{
let actual;
/**
* @param {DirectionType} direction
*/
const callback = (direction) => {
actual = direction
}
const Dom = () => {
const handles = useSwipe({callback});
handles.onMouseDown(/**@type any*/({clientX: 0, clientY: 0}));
handles.onMouseMove(/**@type any*/({clientX: 101, clientY: 0}));
handles.onMouseUp(/**@type any*/({}));
return <div {...handles}/>;
};
render(<Dom />);
await act(()=>{
expect(actual).to.equal("right");
});
});

it("test swipe down", async ()=>{
let actual;
/**
* @param {DirectionType} direction
*/
const callback = (direction) => {
actual = direction
}
const Dom = () => {
const handles = useSwipe({callback});
handles.onMouseDown(/**@type any*/({clientX: 0, clientY: 0}));
handles.onMouseMove(/**@type any*/({clientX: 0, clientY: 101}));
handles.onMouseUp(/**@type any*/({}));
return <div {...handles}/>;
};
render(<Dom />);
await act(()=>{
expect(actual).to.equal("down");
});
});

it("test swipe left", async ()=>{
let actual;
/**
* @param {DirectionType} direction
*/
const callback = (direction) => {
actual = direction
}
const Dom = () => {
const handles = useSwipe({callback});
handles.onMouseDown(/**@type any*/({clientX: 101, clientY: 0}));
handles.onMouseMove(/**@type any*/({clientX: 0, clientY: 0}));
handles.onMouseUp(/**@type any*/({}));
return <div {...handles}/>;
};
render(<Dom />);
await act(()=>{
expect(actual).to.equal("left");
});
});
});
8 changes: 7 additions & 1 deletion packages/reshow-hooks/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ export { default as useLongPress } from "./useLongPress";

export { default as useMounted } from "./useMounted";
export { default as usePrevious } from "./usePrevious";

/**
* @typedef {import('./useSwipe').DirectionType} DirectionType
*/
export { useSwipe } from "./useSwipe";

export { default as useSyncChange } from "./useSyncChange";
export { default as useSyncState } from "./useSyncState";
export { default as useTimer } from "./useTimer";
export { default as useRefWithInitCallback } from "./useRefWithInitCallback";
export { default as useRefUpdate } from "./useRefUpdate";
export { default as useTimer } from "./useTimer";
190 changes: 190 additions & 0 deletions packages/reshow-hooks/src/useSwipe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// @ts-check

import { useRef } from "react";
import get from "get-object-value";

/**
* @typedef {React.TouchEvent|React.MouseEvent} UnifyTouchEvent
*/

/**
* @param {UnifyTouchEvent} e
* @returns {MouseEvent}
*/
const unifyTouch = (e) => get(e, ["changedTouches", 0], e);

/**
* @typedef {UP|RIGHT|DOWN|LEFT} DirectionType
*/
const UP = "up";
const RIGHT = "right";
const DOWN = "down";
const LEFT = "left";

/**
* @type {Record<"UP"|"RIGHT"|"DOWN"|"LEFT", DirectionType>}
*/
const Directions = {
UP,
RIGHT,
DOWN,
LEFT,
};

const defaultThresholdTime = 500;
const defaultThresholdDistance = 100;
const defaultCallback = (/**@type DirectionType*/ _bDirection) => {};

/**
* @typedef {object} UseSwipeProps
* @property {number=} thresholdTime
* @property {number=} thresholdDistance
* @property {function(DirectionType):void} callback=defaultCallback
*/

/**
* @typedef {object} UseSwipeState
* @property {number} thresholdTime
* @property {number} thresholdDistance
* @property {function(DirectionType):void} callback=defaultCallback
* @property {number} startTime
* @property {boolean} bTracking
* @property {Coordinate} startPos
* @property {Coordinate} endPos
*/

/**
* @typedef {object} Coordinate
* @property {number} x
* @property {number} y
*/

/**
* @param {UseSwipeProps} props
*/
export const useSwipe = ({
thresholdTime = defaultThresholdTime,
thresholdDistance = defaultThresholdDistance,
callback = defaultCallback,
}) => {
/**
* @type {React.MutableRefObject<UseSwipeState>}
*/
const lastState = useRef({
thresholdDistance,
thresholdTime,
callback,
startTime: 0,
bTracking: false,
startPos: { x: 0, y: 0 },
endPos: { x: 0, y: 0 },
});
lastState.current = {
...lastState.current,
thresholdDistance,
thresholdTime,
callback,
};

const handler = {
gestureStart: (/**@type UnifyTouchEvent*/ e) => {
const { clientX, clientY } = unifyTouch(e);
lastState.current.bTracking = true;
/* Hack - would normally use e.timeStamp but it's whack in Fx/Android */
lastState.current.startTime = new Date().getTime();
lastState.current.startPos.x = clientX;
lastState.current.startPos.y = clientY;
},
gestureEnd: () => {
const {
callback,
thresholdTime,
thresholdDistance,
startPos,
endPos,
startTime,
} = lastState.current;
lastState.current.bTracking = false;
/**
* @type {DirectionType?}
*/
let direction;
const now = new Date().getTime();
const deltaTime = now - startTime;
const deltaX = endPos.x - startPos.x;
const deltaY = endPos.y - startPos.y;
/* work out what the movement was */
if (deltaTime > thresholdTime) {
/* gesture too slow */
return;
} else {
if (
deltaX > thresholdDistance &&
Math.abs(deltaY) < thresholdDistance
) {
direction = Directions.RIGHT;
} else if (
-deltaX > thresholdDistance &&
Math.abs(deltaY) < thresholdDistance
) {
direction = Directions.LEFT;
} else if (
deltaY > thresholdDistance &&
Math.abs(deltaX) < thresholdDistance
) {
direction = Directions.DOWN;
} else if (
-deltaY > thresholdDistance &&
Math.abs(deltaX) < thresholdDistance
) {
direction = Directions.UP;
} else {
direction = null;
}
if (null != direction) {
callback(direction);
}
}
},
gestureMove: (/**@type UnifyTouchEvent*/ e) => {
if (lastState.current.bTracking) {
const { clientX, clientY } = unifyTouch(e);
lastState.current.endPos.x = clientX;
lastState.current.endPos.y = clientY;
}
},
};
const mouseHandlers = {
/**
* @type {React.MouseEventHandler}
*/
onMouseDown: handler.gestureStart,
/**
* @type {React.MouseEventHandler}
*/
onMouseUp: handler.gestureEnd,
/**
* @type {React.MouseEventHandler}
*/
onMouseMove: handler.gestureMove,
};

const touchHandlers = {
/**
* @type {React.TouchEventHandler}
*/
onTouchStart: handler.gestureStart,
/**
* @type {React.TouchEventHandler}
*/
onTouchEnd: handler.gestureEnd,
/**
* @type {React.TouchEventHandler}
*/
onTouchMove: handler.gestureMove,
};
return {
...mouseHandlers,
...touchHandlers,
};
};
4 changes: 3 additions & 1 deletion packages/reshow-hooks/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ export { default as useLoaded } from "./useLoaded";
export { default as useLongPress } from "./useLongPress";
export { default as useMounted } from "./useMounted";
export { default as usePrevious } from "./usePrevious";
export { useSwipe } from "./useSwipe";
export { default as useSyncChange } from "./useSyncChange";
export { default as useSyncState } from "./useSyncState";
export { default as useTimer } from "./useTimer";
export { default as useRefWithInitCallback } from "./useRefWithInitCallback";
export { default as useRefUpdate } from "./useRefUpdate";
export { default as useTimer } from "./useTimer";
export type LongPressEvent = import("./useLongPress").LongPressEvent;
export type DirectionType = import("./useSwipe").DirectionType;
52 changes: 52 additions & 0 deletions packages/reshow-hooks/types/useSwipe.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export function useSwipe({ thresholdTime, thresholdDistance, callback, }: UseSwipeProps): {
/**
* @type {React.TouchEventHandler}
*/
onTouchStart: React.TouchEventHandler;
/**
* @type {React.TouchEventHandler}
*/
onTouchEnd: React.TouchEventHandler;
/**
* @type {React.TouchEventHandler}
*/
onTouchMove: React.TouchEventHandler;
/**
* @type {React.MouseEventHandler}
*/
onMouseDown: React.MouseEventHandler;
/**
* @type {React.MouseEventHandler}
*/
onMouseUp: React.MouseEventHandler;
/**
* @type {React.MouseEventHandler}
*/
onMouseMove: React.MouseEventHandler;
};
export type UnifyTouchEvent = React.TouchEvent | React.MouseEvent;
export type DirectionType = "up" | "right" | "down" | "left";
export type UseSwipeProps = {
thresholdTime?: number | undefined;
thresholdDistance?: number | undefined;
/**
* =defaultCallback
*/
callback: (arg0: DirectionType) => void;
};
export type UseSwipeState = {
thresholdTime: number;
thresholdDistance: number;
/**
* =defaultCallback
*/
callback: (arg0: DirectionType) => void;
startTime: number;
bTracking: boolean;
startPos: Coordinate;
endPos: Coordinate;
};
export type Coordinate = {
x: number;
y: number;
};
Loading

0 comments on commit 37d7614

Please sign in to comment.