Skip to content

Commit

Permalink
add very simple long press functionality,
Browse files Browse the repository at this point in the history
  • Loading branch information
bmcmahen committed May 21, 2019
1 parent f2386b2 commit 299dac1
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 10 deletions.
77 changes: 67 additions & 10 deletions src/index.ts
Expand Up @@ -25,22 +25,26 @@ import { useGestureResponder } from "react-gesture-responder";

const HIGHLIGHT_DELAY_MS = 100;
const PRESS_EXPAND_PX = 20;
const LONG_PRESS_DELAY = 500 - HIGHLIGHT_DELAY_MS;

type States =
| "ERROR"
| "NOT_RESPONDER"
| "RESPONDER_ACTIVE_IN"
| "RESPONDER_ACTIVE_OUT"
| "RESPONDER_PRESSED_IN"
| "RESPONDER_PRESSED_OUT";
| "RESPONDER_PRESSED_OUT"
| "RESPONDER_LONG_PRESSED_IN"
| "RESPONDER_LONG_PRESSED_OUT";

type Events =
| "DELAY"
| "RESPONDER_GRANT"
| "RESPONDER_RELEASE"
| "RESPONDER_TERMINATED"
| "ENTER_PRESS_RECT"
| "LEAVE_PRESS_RECT";
| "LEAVE_PRESS_RECT"
| "LONG_PRESS_DETECTED";

type TransitionsType = { [key in States]: TransitionType };

Expand All @@ -53,47 +57,71 @@ const transitions = {
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "NOT_RESPONDER",
LEAVE_PRESS_RECT: "NOT_RESPONDER"
LEAVE_PRESS_RECT: "NOT_RESPONDER",
LONG_PRESS_DETECTED: "NOT_RESPONDER"
},
RESPONDER_ACTIVE_IN: {
DELAY: "RESPONDER_PRESSED_IN",
RESPONDER_GRANT: "ERROR",
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "RESPONDER_ACTIVE_IN",
LEAVE_PRESS_RECT: "RESPONDER_ACTIVE_OUT"
LEAVE_PRESS_RECT: "RESPONDER_ACTIVE_OUT",
LONG_PRESS_DETECTED: "ERROR"
},
RESPONDER_ACTIVE_OUT: {
DELAY: "RESPONDER_PRESSED_OUT",
RESPONDER_GRANT: "ERROR",
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "RESPONDER_ACTIVE_IN",
LEAVE_PRESS_RECT: "RESPONDER_ACTIVE_OUT"
LEAVE_PRESS_RECT: "RESPONDER_ACTIVE_OUT",
LONG_PRESS_DETECTED: "ERROR"
},
RESPONDER_PRESSED_IN: {
DELAY: "ERROR",
RESPONDER_GRANT: "ERROR",
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "RESPONDER_PRESSED_IN",
LEAVE_PRESS_RECT: "RESPONDER_PRESSED_OUT"
LEAVE_PRESS_RECT: "RESPONDER_PRESSED_OUT",
LONG_PRESS_DETECTED: "RESPONDER_LONG_PRESSED_IN"
},
RESPONDER_PRESSED_OUT: {
DELAY: "ERROR",
RESPONDER_GRANT: "ERROR",
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "RESPONDER_PRESSED_IN",
LEAVE_PRESS_RECT: "RESPONDER_PRESSED_OUT"
LEAVE_PRESS_RECT: "RESPONDER_PRESSED_OUT",
LONG_PRESS_DETECTED: "ERROR"
},
RESPONDER_LONG_PRESSED_IN: {
DELAY: "ERROR",
RESPONDER_GRANT: "ERROR",
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "RESPONDER_PRESSED_IN",
LEAVE_PRESS_RECT: "RESPONDER_LONG_PRESSED_OUT",
LONG_PRESS_DETECTED: "RESPONDER_LONG_PRESSED_IN"
},
RESPONDER_LONG_PRESSED_OUT: {
DELAY: "ERROR",
RESPONDER_GRANT: "ERROR",
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "RESPONDER_LONG_PRESSED_IN",
LEAVE_PRESS_RECT: "RESPONDER_LONG_PRESSED_OUT",
LONG_PRESS_DETECTED: "ERROR"
},
ERROR: {
DELAY: "NOT_RESPONDER",
RESPONDER_GRANT: "RESPONDER_ACTIVE_IN",
RESPONDER_RELEASE: "NOT_RESPONDER",
RESPONDER_TERMINATED: "NOT_RESPONDER",
ENTER_PRESS_RECT: "NOT_RESPONDER",
LEAVE_PRESS_RECT: "NOT_RESPONDER"
LEAVE_PRESS_RECT: "NOT_RESPONDER",
LONG_PRESS_DETECTED: "NOT_RESPONDER"
}
} as TransitionsType;

Expand All @@ -108,6 +136,7 @@ export interface TouchableOptions {
disabled: boolean;
terminateOnScroll: boolean;
onPress?: OnPressFunction;
onLongPress?: OnPressFunction;
}

const defaultOptions: TouchableOptions = {
Expand All @@ -116,12 +145,14 @@ const defaultOptions: TouchableOptions = {
behavior: "button",
disabled: false,
terminateOnScroll: true,
onPress: undefined
onPress: undefined,
onLongPress: undefined
};

export function useTouchable(options: Partial<TouchableOptions> = {}) {
const {
onPress,
onLongPress,
terminateOnScroll,
delay,
behavior,
Expand All @@ -133,6 +164,7 @@ export function useTouchable(options: Partial<TouchableOptions> = {}) {
const disabled = localDisabled;
const ref = React.useRef<HTMLAnchorElement | HTMLDivElement | any>(null);
const delayTimer = React.useRef<number>();
const longDelayTimer = React.useRef<number>();
const bounds = React.useRef<ClientRect>();
const [hover, setHover] = React.useState(false);
const [showHover, setShowHover] = React.useState(true);
Expand All @@ -148,14 +180,18 @@ export function useTouchable(options: Partial<TouchableOptions> = {}) {
const nextState = transitions[state.current][event];
state.current = nextState;

if (nextState === "RESPONDER_PRESSED_IN") {
if (
nextState === "RESPONDER_PRESSED_IN" ||
nextState === "RESPONDER_LONG_PRESSED_IN"
) {
setActive(true);
} else {
setActive(false);
}

if (nextState === "NOT_RESPONDER") {
clearTimeout(delayTimer.current);
clearTimeout(longDelayTimer.current);
}
}

Expand Down Expand Up @@ -183,6 +219,12 @@ export function useTouchable(options: Partial<TouchableOptions> = {}) {
}
}

function emitLongPress() {
if (!disabled && onLongPress) {
onLongPress();
}
}

function bindScroll() {
if (terminateOnScroll) {
document.addEventListener("scroll", onScroll, true);
Expand Down Expand Up @@ -210,14 +252,25 @@ export function useTouchable(options: Partial<TouchableOptions> = {}) {
delayPressMs > 0
? window.setTimeout(afterDelay, delayPressMs)
: undefined;

if (delayPressMs === 0) {
dispatch("DELAY");
}

longDelayTimer.current = window.setTimeout(
afterLongDelay,
LONG_PRESS_DELAY
);

bindScroll();
setShowHover(false);
}

function afterLongDelay() {
dispatch("LONG_PRESS_DETECTED");
emitLongPress();
}

// onTerminate should be disambiguated from onRelease
// because it should never trigger onPress events.
function onTerminate() {
Expand Down Expand Up @@ -275,12 +328,15 @@ export function useTouchable(options: Partial<TouchableOptions> = {}) {
return;
}

clearTimeout(longDelayTimer.current);

const { clientX, clientY } = e.touches && e.touches[0] ? e.touches[0] : e;
const withinBounds = isWithinActiveBounds(
clientX,
clientY,
bounds.current!
);

if (withinBounds) {
dispatch("ENTER_PRESS_RECT");
} else {
Expand Down Expand Up @@ -330,6 +386,7 @@ export function useTouchable(options: Partial<TouchableOptions> = {}) {
React.useEffect(() => {
return () => {
clearTimeout(delayTimer.current);
clearTimeout(longDelayTimer.current);
unbindScroll();
};
}, []);
Expand Down
5 changes: 5 additions & 0 deletions stories/intro.stories.tsx
Expand Up @@ -11,8 +11,13 @@ function TouchableHighlight({ options = {} }) {
setPressCount(pressCount + 1);
}

function onLongPress() {
console.log("LONG PRESSED");
}

const { bind, active, hover } = useTouchable({
onPress,
onLongPress,
behavior: "button",
...options
});
Expand Down

0 comments on commit 299dac1

Please sign in to comment.