Skip to content

Commit

Permalink
Add delay props to Hover event module
Browse files Browse the repository at this point in the history
  • Loading branch information
necolas committed Apr 4, 2019
1 parent 937d262 commit dfaf560
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 49 deletions.
156 changes: 111 additions & 45 deletions packages/react-events/src/Hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ type HoverProps = {
};

type HoverState = {
isActiveHovered: boolean,
isHovered: boolean,
isInHitSlop: boolean,
isTouched: boolean,
hoverStartTimeout: null | TimeoutID,
hoverEndTimeout: null | TimeoutID,
};

type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange';
Expand Down Expand Up @@ -60,71 +63,137 @@ function createHoverEvent(
};
}

function dispatchHoverChangeEvent(
context: EventResponderContext,
props: HoverProps,
state: HoverState,
): void {
const listener = () => {
props.onHoverChange(state.isActiveHovered);
};
const syntheticEvent = createHoverEvent(
'hoverchange',
context.eventTarget,
listener,
);
context.dispatchEvent(syntheticEvent, {discrete: true});
}

function dispatchHoverStartEvents(
context: EventResponderContext,
props: HoverProps,
state: HoverState,
): void {
const {event, eventTarget} = context;
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
return;
}
if (props.onHoverStart) {
const syntheticEvent = createHoverEvent(
'hoverstart',
eventTarget,
props.onHoverStart,
);
context.dispatchEvent(syntheticEvent, {discrete: true});

state.isHovered = true;

if (state.hoverEndTimeout !== null) {
clearTimeout(state.hoverEndTimeout);
state.hoverEndTimeout = null;
}
if (props.onHoverChange) {
const listener = () => {
props.onHoverChange(true);
};
const syntheticEvent = createHoverEvent(
'hoverchange',
eventTarget,
listener,
);
context.dispatchEvent(syntheticEvent, {discrete: true});

const dispatch = () => {
state.isActiveHovered = true;

if (props.onHoverStart) {
const syntheticEvent = createHoverEvent(
'hoverstart',
eventTarget,
props.onHoverStart,
);
context.dispatchEvent(syntheticEvent, {discrete: true});
}
if (props.onHoverChange) {
dispatchHoverChangeEvent(context, props, state);
}
};

if (!state.isActiveHovered) {
const delay = calculateDelayMS(props.delayHoverStart, 0, 0);
if (delay > 0) {
state.hoverStartTimeout = setTimeout(
() =>
context.withAsyncDispatching(() => {
state.hoverStartTimeout = null;
dispatch();
}),
delay,
);
} else {
dispatch();
}
}
}

function dispatchHoverEndEvents(
context: EventResponderContext,
props: HoverProps,
state: HoverState,
) {
const {event, eventTarget} = context;

if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
return;
}
if (props.onHoverEnd) {
const syntheticEvent = createHoverEvent(
'hoverend',
eventTarget,
props.onHoverEnd,
);
context.dispatchEvent(syntheticEvent, {discrete: true});

state.isHovered = false;

if (state.hoverStartTimeout !== null) {
clearTimeout(state.hoverStartTimeout);
state.hoverStartTimeout = null;
}
if (props.onHoverChange) {
const listener = () => {
props.onHoverChange(false);
};
const syntheticEvent = createHoverEvent(
'hoverchange',
eventTarget,
listener,
);
context.dispatchEvent(syntheticEvent, {discrete: true});

const dispatch = () => {
state.isActiveHovered = false;

if (props.onHoverEnd) {
const syntheticEvent = createHoverEvent(
'hoverend',
eventTarget,
props.onHoverEnd,
);
context.dispatchEvent(syntheticEvent, {discrete: true});
}
if (props.onHoverChange) {
dispatchHoverChangeEvent(context, props, state);
}
};

if (state.isActiveHovered) {
const delay = calculateDelayMS(props.delayHoverEnd, 0, 0);
if (delay > 0) {
state.hoverEndTimeout = setTimeout(
() =>
context.withAsyncDispatching(() => {
dispatch();
}),
delay,
);
} else {
dispatch();
}
}
}

function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
const maybeNumber = delay == null ? null : delay;
return Math.max(min, maybeNumber != null ? maybeNumber : fallback);
}

const HoverResponder = {
targetEventTypes,
createInitialState() {
return {
isActiveHovered: false,
isHovered: false,
isInHitSlop: false,
isTouched: false,
hoverStartTimeout: null,
hoverEndTimeout: null,
};
},
handleEvent(
Expand Down Expand Up @@ -165,32 +234,30 @@ const HoverResponder = {
state.isInHitSlop = true;
return;
}
dispatchHoverStartEvents(context, props);
state.isHovered = true;
dispatchHoverStartEvents(context, props, state);
}
break;
}
case 'pointerout':
case 'mouseout': {
if (state.isHovered && !state.isTouched) {
dispatchHoverEndEvents(context, props);
state.isHovered = false;
dispatchHoverEndEvents(context, props, state);
}
state.isInHitSlop = false;
state.isTouched = false;
break;
}

case 'pointermove': {
if (!state.isTouched) {
if (state.isHovered && !state.isTouched) {
if (state.isInHitSlop) {
if (
!context.isPositionWithinTouchHitTarget(
(event: any).x,
(event: any).y,
)
) {
dispatchHoverStartEvents(context, props);
state.isHovered = true;
dispatchHoverStartEvents(context, props, state);
state.isInHitSlop = false;
}
} else if (
Expand All @@ -200,17 +267,16 @@ const HoverResponder = {
(event: any).y,
)
) {
dispatchHoverEndEvents(context, props);
state.isHovered = false;
dispatchHoverEndEvents(context, props, state);
state.isInHitSlop = true;
}
}
break;
}

case 'pointercancel': {
if (state.isHovered && !state.isTouched) {
dispatchHoverEndEvents(context, props);
state.isHovered = false;
dispatchHoverEndEvents(context, props, state);
state.isTouched = false;
}
break;
Expand Down
Loading

0 comments on commit dfaf560

Please sign in to comment.