Migrate to a monorepo structure (#2909)

This commit is contained in:
Charles Bochet
2023-12-10 18:10:54 +01:00
committed by GitHub
parent a70a9281eb
commit 5bdca9de6c
2304 changed files with 37152 additions and 25869 deletions

View File

@ -0,0 +1,39 @@
import { useRef } from 'react';
import { fireEvent, render } from '@testing-library/react';
import { useListenClickOutside } from '../useListenClickOutside';
const onOutsideClick = jest.fn();
const TestComponentDomMode = () => {
const buttonRef = useRef<HTMLButtonElement>(null);
const buttonRef2 = useRef<HTMLButtonElement>(null);
useListenClickOutside({
refs: [buttonRef, buttonRef2],
callback: onOutsideClick,
});
return (
<div>
<span>Outside</span>
<button ref={buttonRef}>Inside</button>
<button ref={buttonRef2}>Inside 2</button>
</div>
);
};
test('useListenClickOutside web-hook works in dom mode', async () => {
const { getByText } = render(<TestComponentDomMode />);
const inside = getByText('Inside');
const inside2 = getByText('Inside 2');
const outside = getByText('Outside');
fireEvent.click(inside);
expect(onOutsideClick).toHaveBeenCalledTimes(0);
fireEvent.click(inside2);
expect(onOutsideClick).toHaveBeenCalledTimes(0);
fireEvent.click(outside);
expect(onOutsideClick).toHaveBeenCalledTimes(1);
});

View File

@ -0,0 +1,195 @@
import React, { useEffect, useState } from 'react';
export enum ClickOutsideMode {
comparePixels = 'comparePixels',
compareHTMLRef = 'compareHTMLRef',
}
export const useListenClickOutside = <T extends Element>({
refs,
callback,
mode = ClickOutsideMode.compareHTMLRef,
enabled = true,
}: {
refs: Array<React.RefObject<T>>;
callback: (event: MouseEvent | TouchEvent) => void;
mode?: ClickOutsideMode;
enabled?: boolean;
}) => {
const [isMouseDownInside, setIsMouseDownInside] = useState(false);
useEffect(() => {
const handleMouseDown = (event: MouseEvent | TouchEvent) => {
if (mode === ClickOutsideMode.compareHTMLRef) {
const clickedOnAtLeastOneRef = refs
.filter((ref) => !!ref.current)
.some((ref) => ref.current?.contains(event.target as Node));
setIsMouseDownInside(clickedOnAtLeastOneRef);
}
if (mode === ClickOutsideMode.comparePixels) {
const clickedOnAtLeastOneRef = refs
.filter((ref) => !!ref.current)
.some((ref) => {
if (!ref.current) {
return false;
}
const { x, y, width, height } = ref.current.getBoundingClientRect();
const clientX =
'clientX' in event
? event.clientX
: event.changedTouches[0].clientX;
const clientY =
'clientY' in event
? event.clientY
: event.changedTouches[0].clientY;
if (
clientX < x ||
clientX > x + width ||
clientY < y ||
clientY > y + height
) {
return false;
}
return true;
});
setIsMouseDownInside(clickedOnAtLeastOneRef);
}
};
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
if (mode === ClickOutsideMode.compareHTMLRef) {
const clickedOnAtLeastOneRef = refs
.filter((ref) => !!ref.current)
.some((ref) => ref.current?.contains(event.target as Node));
if (!clickedOnAtLeastOneRef && !isMouseDownInside) {
callback(event);
}
}
if (mode === ClickOutsideMode.comparePixels) {
const clickedOnAtLeastOneRef = refs
.filter((ref) => !!ref.current)
.some((ref) => {
if (!ref.current) {
return false;
}
const { x, y, width, height } = ref.current.getBoundingClientRect();
const clientX =
'clientX' in event
? event.clientX
: event.changedTouches[0].clientX;
const clientY =
'clientY' in event
? event.clientY
: event.changedTouches[0].clientY;
if (
clientX < x ||
clientX > x + width ||
clientY < y ||
clientY > y + height
) {
return false;
}
return true;
});
if (!clickedOnAtLeastOneRef && !isMouseDownInside) {
callback(event);
}
}
};
if (enabled) {
document.addEventListener('mousedown', handleMouseDown, {
capture: true,
});
document.addEventListener('click', handleClickOutside, { capture: true });
document.addEventListener('touchstart', handleMouseDown, {
capture: true,
});
document.addEventListener('touchend', handleClickOutside, {
capture: true,
});
return () => {
document.removeEventListener('mousedown', handleMouseDown, {
capture: true,
});
document.removeEventListener('click', handleClickOutside, {
capture: true,
});
document.removeEventListener('touchstart', handleMouseDown, {
capture: true,
});
document.removeEventListener('touchend', handleClickOutside, {
capture: true,
});
};
}
}, [refs, callback, mode, enabled, isMouseDownInside]);
};
export const useListenClickOutsideByClassName = ({
classNames,
excludeClassNames,
callback,
}: {
classNames: string[];
excludeClassNames?: string[];
callback: () => void;
}) => {
useEffect(() => {
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
if (!(event.target instanceof Node)) return;
const clickedElement = event.target as HTMLElement;
let isClickedInside = false;
let isClickedOnExcluded = false;
let currentElement: HTMLElement | null = clickedElement;
while (currentElement) {
const currentClassList = currentElement.classList;
isClickedInside = classNames.some((className) =>
currentClassList.contains(className),
);
isClickedOnExcluded =
excludeClassNames?.some((className) =>
currentClassList.contains(className),
) ?? false;
if (isClickedInside || isClickedOnExcluded) {
break;
}
currentElement = currentElement.parentElement;
}
if (!isClickedInside && !isClickedOnExcluded) {
callback();
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchend', handleClickOutside, {
capture: true,
});
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchend', handleClickOutside, {
capture: true,
});
};
}, [callback, classNames, excludeClassNames]);
};

View File

@ -0,0 +1,67 @@
import { useCallback, useEffect } from 'react';
type MouseListener = (positionX: number, positionY: number) => void;
export const useTrackPointer = ({
shouldTrackPointer = true,
onMouseMove,
onMouseDown,
onMouseUp,
}: {
shouldTrackPointer?: boolean;
onMouseMove?: MouseListener;
onMouseDown?: MouseListener;
onMouseUp?: MouseListener;
}) => {
const extractPosition = useCallback((event: MouseEvent | TouchEvent) => {
const clientX =
'clientX' in event ? event.clientX : event.changedTouches[0].clientX;
const clientY =
'clientY' in event ? event.clientY : event.changedTouches[0].clientY;
return { clientX, clientY };
}, []);
const onInternalMouseMove = useCallback(
(event: MouseEvent | TouchEvent) => {
const { clientX, clientY } = extractPosition(event);
onMouseMove?.(clientX, clientY);
},
[onMouseMove, extractPosition],
);
const onInternalMouseDown = useCallback(
(event: MouseEvent | TouchEvent) => {
const { clientX, clientY } = extractPosition(event);
onMouseDown?.(clientX, clientY);
},
[onMouseDown, extractPosition],
);
const onInternalMouseUp = useCallback(
(event: MouseEvent | TouchEvent) => {
const { clientX, clientY } = extractPosition(event);
onMouseUp?.(clientX, clientY);
},
[onMouseUp, extractPosition],
);
useEffect(() => {
if (shouldTrackPointer) {
document.addEventListener('mousemove', onInternalMouseMove);
document.addEventListener('mousedown', onInternalMouseDown);
document.addEventListener('mouseup', onInternalMouseUp);
return () => {
document.removeEventListener('mousemove', onInternalMouseMove);
document.removeEventListener('mousedown', onInternalMouseDown);
document.removeEventListener('mouseup', onInternalMouseUp);
};
}
}, [
shouldTrackPointer,
onInternalMouseMove,
onInternalMouseDown,
onInternalMouseUp,
]);
};