Skip to content

Commit

Permalink
review draggable logic
Browse files Browse the repository at this point in the history
- use pointer events
- make distinction between touch and mouse
  • Loading branch information
fkhadra committed Jul 9, 2023
1 parent 4665865 commit c386141
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 57 deletions.
1 change: 1 addition & 0 deletions scss/_toast.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.#{$rt-namespace}__toast {
position: relative;
touch-action: none;
min-height: var(--toastify-toast-min-height);
box-sizing: border-box;
margin-bottom: 1rem;
Expand Down
25 changes: 25 additions & 0 deletions src/core/toast.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,31 @@ describe('with container', () => {
cy.findByText('msg').should('not.exist');
});

it('pause and resume notification', () => {
const id = toast('msg', {
autoClose: 10000
});

cy.findByRole('progressbar').as('progressBar');

cy.get('@progressBar')
.should('have.attr', 'style')
.and('include', 'animation-play-state: running')
.then(() => {
toast.pause({ id });
cy.get('@progressBar')
.should('have.attr', 'style')
.and('include', 'animation-play-state: paused')
.then(() => {
toast.play({ id });

cy.get('@progressBar')
.should('have.attr', 'style')
.and('include', 'animation-play-state: running');
});
});
});

describe('update function', () => {
it('update an existing toast', () => {
const id = toast('msg');
Expand Down
75 changes: 19 additions & 56 deletions src/hooks/useToast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { registerToggle } from '../core/store';

interface Draggable {
start: number;
x: number;
y: number;
delta: number;
removalDistance: number;
canCloseOnClick: boolean;
Expand All @@ -16,28 +14,12 @@ interface Draggable {
didMove: boolean;
}

type DragEvent = MouseEvent & TouchEvent;

function getX(e: DragEvent) {
return e.targetTouches && e.targetTouches.length >= 1
? e.targetTouches[0].clientX
: e.clientX;
}

function getY(e: DragEvent) {
return e.targetTouches && e.targetTouches.length >= 1
? e.targetTouches[0].clientY
: e.clientY;
}

export function useToast(props: ToastProps) {
const [isRunning, setIsRunning] = useState(false);
const [preventExitTransition, setPreventExitTransition] = useState(false);
const toastRef = useRef<HTMLDivElement>(null);
const drag = useRef<Draggable>({
start: 0,
x: 0,
y: 0,
delta: 0,
removalDistance: 0,
canCloseOnClick: true,
Expand All @@ -60,28 +42,21 @@ export function useToast(props: ToastProps) {
};
}, [props.pauseOnFocusLoss]);

Check warning on line 43 in src/hooks/useToast.ts

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has missing dependencies: 'bindFocusEvents' and 'unbindFocusEvents'. Either include them or remove the dependency array

Check warning on line 43 in src/hooks/useToast.ts

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has missing dependencies: 'bindFocusEvents' and 'unbindFocusEvents'. Either include them or remove the dependency array

function onDragStart(
e: React.MouseEvent<HTMLElement, MouseEvent> | React.TouchEvent<HTMLElement>
) {
if (props.draggable) {
// required for ios safari to prevent default swipe behavior
if (e.nativeEvent.type === 'touchstart') e.nativeEvent.preventDefault();

function onDragStart(e: React.PointerEvent<HTMLElement>) {
if (props.draggable === true || props.draggable === e.pointerType) {
bindDragEvents();
const toast = toastRef.current!;
drag.canCloseOnClick = true;
drag.canDrag = true;
drag.boundingRect = toast.getBoundingClientRect();
toast.style.transition = '';
drag.x = getX(e.nativeEvent as DragEvent);
drag.y = getY(e.nativeEvent as DragEvent);

if (props.draggableDirection === Direction.X) {
drag.start = drag.x;
drag.start = e.clientX;
drag.removalDistance =
toast.offsetWidth * (props.draggablePercent / 100);
} else {
drag.start = drag.y;
drag.start = e.clientY;
drag.removalDistance =
(toast.offsetHeight *
(props.draggablePercent === Default.DRAGGABLE_PERCENT
Expand All @@ -92,19 +67,17 @@ export function useToast(props: ToastProps) {
}
}

function onDragTransitionEnd(
e: React.MouseEvent<HTMLElement, MouseEvent> | React.TouchEvent<HTMLElement>
) {
function onDragTransitionEnd(e: React.PointerEvent<HTMLElement>) {
if (drag.boundingRect) {
const { top, bottom, left, right } = drag.boundingRect;

if (
e.nativeEvent.type !== 'touchend' &&
props.pauseOnHover &&
drag.x >= left &&
drag.x <= right &&
drag.y >= top &&
drag.y <= bottom
e.clientX >= left &&
e.clientX <= right &&
e.clientY >= top &&
e.clientY <= bottom
) {
pauseToast();
} else {
Expand Down Expand Up @@ -135,36 +108,28 @@ export function useToast(props: ToastProps) {

function bindDragEvents() {
drag.didMove = false;
document.addEventListener('mousemove', onDragMove);
document.addEventListener('mouseup', onDragEnd);

document.addEventListener('touchmove', onDragMove);
document.addEventListener('touchend', onDragEnd);
document.addEventListener('pointermove', onDragMove);
document.addEventListener('pointerup', onDragEnd);
}

function unbindDragEvents() {
document.removeEventListener('mousemove', onDragMove);
document.removeEventListener('mouseup', onDragEnd);

document.removeEventListener('touchmove', onDragMove);
document.removeEventListener('touchend', onDragEnd);
document.removeEventListener('pointermove', onDragMove);
document.removeEventListener('pointerup', onDragEnd);
}

function onDragMove(e: MouseEvent | TouchEvent) {
function onDragMove(e: PointerEvent) {
const toast = toastRef.current!;
if (drag.canDrag && toast) {
drag.didMove = true;
if (isRunning) pauseToast();
drag.x = getX(e as DragEvent);
drag.y = getY(e as DragEvent);
if (props.draggableDirection === Direction.X) {
drag.delta = drag.x - drag.start;
drag.delta = e.clientX - drag.start;
} else {
drag.delta = drag.y - drag.start;
drag.delta = e.clientY - drag.start;
}

// prevent false positive during a toast click
if (drag.start !== drag.x) drag.canCloseOnClick = false;
if (drag.start !== e.clientX) drag.canCloseOnClick = false;
toast.style.transform = `translate${props.draggableDirection}(${drag.delta}px)`;
toast.style.opacity = `${
1 - Math.abs(drag.delta / drag.removalDistance)
Expand All @@ -189,10 +154,8 @@ export function useToast(props: ToastProps) {
}

const eventHandlers: DOMAttributes<HTMLElement> = {
onMouseDown: onDragStart,
onTouchStart: onDragStart,
onMouseUp: onDragTransitionEnd,
onTouchEnd: onDragTransitionEnd
onPointerDown: onDragStart,
onPointerUp: onDragTransitionEnd
};

if (autoClose && pauseOnHover) {
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ interface CommonOptions {
* Allow toast to be draggable
* `Default: true`
*/
draggable?: boolean;
draggable?: boolean | 'mouse' | 'touch';

/**
* The percentage of the toast's width it takes for a drag to dismiss a toast
Expand Down

0 comments on commit c386141

Please sign in to comment.