make useAction...ClickedOutside use an array of elements to click outside
This commit is contained in:
parent
a6231610a7
commit
e0d0b2a9df
@ -20,7 +20,7 @@ const Overlay: FC<OverlayProps> = (props: OverlayProps) => {
|
||||
if (childRootRef) {
|
||||
// this is alright to do since childRootRef (the ref itself) should never change for each component using this element
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useActionWhenEscapeOrClickedOrContextOutsideEffect(childRootRef, () => setOverlay(null));
|
||||
useActionWhenEscapeOrClickedOrContextOutsideEffect([childRootRef], () => setOverlay(null));
|
||||
}
|
||||
|
||||
const keyDownHandler = useCallback(
|
||||
|
@ -18,10 +18,16 @@ const ContextMenu: FC<ContextMenuProps> = (props: ContextMenuProps) => {
|
||||
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useActionWhenEscapeOrClickedOrContextOutsideEffect(rootRef, close);
|
||||
useActionWhenEscapeOrClickedOrContextOutsideEffect([rootRef], close);
|
||||
|
||||
return (
|
||||
<Context rootRef={rootRef} relativeToRef={relativeToRef} relativeToPos={relativeToPos} alignment={alignment} realignDeps={realignDeps}>
|
||||
<Context
|
||||
rootRef={rootRef}
|
||||
relativeToRef={relativeToRef}
|
||||
relativeToPos={relativeToPos}
|
||||
alignment={alignment}
|
||||
realignDeps={realignDeps}
|
||||
>
|
||||
<div className="menu">{children}</div>
|
||||
</Context>
|
||||
);
|
||||
|
@ -7,11 +7,19 @@ import React, { FC, useCallback, useMemo, useRef } from 'react';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import ElementsUtil, { IAlignment } from '../require/elements-util';
|
||||
import DownloadButton from '../components/button-download';
|
||||
import { useContextClickContextMenu } from '../require/react-helper';
|
||||
import {
|
||||
useActionWhenEscapeOrClickedOrContextOutsideEffect,
|
||||
useContextClickContextMenu,
|
||||
} from '../require/react-helper';
|
||||
import ImageContextMenu from '../contexts/context-menu-image';
|
||||
import Overlay from '../components/overlay';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { guildResourceSoftImgSrcState, guildResourceState, useRecoilValueSoftImgSrc } from '../require/atoms';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
guildResourceSoftImgSrcState,
|
||||
guildResourceState,
|
||||
overlayState,
|
||||
useRecoilValueSoftImgSrc,
|
||||
} from '../require/atoms';
|
||||
import { isFailed, isLoaded } from '../require/loadables';
|
||||
|
||||
export interface ImageOverlayProps {
|
||||
@ -23,7 +31,11 @@ export interface ImageOverlayProps {
|
||||
const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
|
||||
const { guild, resourceId, resourceName } = props;
|
||||
|
||||
const setOverlay = useSetRecoilState(overlayState);
|
||||
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const dltxtRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// NOTE: These will update together. How convenient!
|
||||
const resource = useRecoilValue(guildResourceState({ guildId: guild.id, resourceId }));
|
||||
@ -46,13 +58,7 @@ const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
|
||||
[resource, resourceName],
|
||||
);
|
||||
|
||||
const onBaseClick = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!rootRef.current) return 1;
|
||||
return 2;
|
||||
},
|
||||
[rootRef],
|
||||
);
|
||||
useActionWhenEscapeOrClickedOrContextOutsideEffect([imgRef, dltxtRef], () => setOverlay(null));
|
||||
|
||||
const sizeText = useMemo(() => {
|
||||
if (isFailed(resource)) return 'Failed to load';
|
||||
@ -61,10 +67,10 @@ const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
|
||||
}, [resource]);
|
||||
|
||||
return (
|
||||
<Overlay childRootRef={rootRef}>
|
||||
<Overlay>
|
||||
<div ref={rootRef} className="content popup-image">
|
||||
<img src={imgSrc} alt={resourceName} title={resourceName} onContextMenu={onContextMenu} />
|
||||
<div className="download">
|
||||
<img ref={imgRef} src={imgSrc} alt={resourceName} title={resourceName} onContextMenu={onContextMenu} />
|
||||
<div ref={dltxtRef} className="download">
|
||||
<div className="info">
|
||||
<div className="name">{resourceName}</div>
|
||||
<div className="size">{sizeText}</div>
|
||||
|
@ -362,75 +362,86 @@ export function useScrollableCallables<T>(
|
||||
// makes sure to also allow you to fly-out a click starting inside of the ref'd element but was dragged outside
|
||||
/** calls the close action when you hit escape or click outside of the ref element */
|
||||
export function useActionWhenEscapeOrClickedOrContextOutsideEffect(
|
||||
ref: RefObject<HTMLElement>,
|
||||
refs: RefObject<HTMLElement>[],
|
||||
actionFunc: () => void,
|
||||
) {
|
||||
// have to use a ref here and not states since we can't re-assign state between mouseup and click
|
||||
const mouseRef = useRef<{ mouseDownTarget: Node | null; mouseUpTarget: Node | null }>({
|
||||
mouseDownTarget: null,
|
||||
mouseUpTarget: null,
|
||||
});
|
||||
const data = useRef<
|
||||
Map<number, { ref: RefObject<HTMLElement>; mouseDownTarget: Node | null; mouseUpTarget: Node | null }>
|
||||
>(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
data.current.clear();
|
||||
for (let i = 0; i < refs.length; ++i) {
|
||||
const ref = refs[i] as RefObject<HTMLElement>;
|
||||
data.current.set(i, { ref, mouseDownTarget: null, mouseUpTarget: null });
|
||||
}
|
||||
}, [refs]);
|
||||
|
||||
const handleClickOutside = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
//console.log('current:', ref.current, 'target:', event.target, 'mouseDownTarget:', mouseRef.current.mouseDownTarget, 'mouseUpTarget:', mouseRef.current.mouseUpTarget);
|
||||
for (const [_idx, { ref, mouseDownTarget, mouseUpTarget }] of data.current) {
|
||||
if (!ref.current) return;
|
||||
|
||||
if (!ref.current) return;
|
||||
// event is inside this ref's element, do not run the action func
|
||||
// casting here is OK. https://stackoverflow.com/q/61164018
|
||||
if (ref.current.contains(event.target as Node)) return;
|
||||
|
||||
// casting here is OK. https://stackoverflow.com/q/61164018
|
||||
if (ref.current.contains(event.target as Node)) return;
|
||||
|
||||
if (mouseRef.current.mouseDownTarget !== null || mouseRef.current.mouseUpTarget !== null) return;
|
||||
if (mouseDownTarget !== null || mouseUpTarget !== null) return;
|
||||
}
|
||||
|
||||
actionFunc();
|
||||
},
|
||||
[ref, mouseRef, actionFunc],
|
||||
[actionFunc],
|
||||
);
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (!ref.current) return;
|
||||
//console.log('mouse down. Contains: ' + ref.current.contains(event.target as Node));
|
||||
if (ref.current.contains(event.target as Node)) {
|
||||
mouseRef.current.mouseDownTarget = event.target as Node;
|
||||
const handleMouseDown = useCallback((event: MouseEvent) => {
|
||||
//console.log('mouse down. Contains: ' + ref.current.contains(event.target as Node));
|
||||
for (const [_idx, dataElement] of data.current) {
|
||||
if (!dataElement.ref.current) return;
|
||||
if (dataElement.ref.current.contains(event.target as Node)) {
|
||||
dataElement.mouseDownTarget = event.target as Node;
|
||||
} else {
|
||||
mouseRef.current.mouseDownTarget = null;
|
||||
dataElement.mouseDownTarget = null;
|
||||
}
|
||||
},
|
||||
[ref, mouseRef],
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleMouseUp = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (!ref.current) return;
|
||||
//console.log('mouse up. Contains: ' + ref.current.contains(event.target as Node));
|
||||
if (ref.current.contains(event.target as Node)) {
|
||||
mouseRef.current.mouseUpTarget = event.target as Node;
|
||||
const handleMouseUp = useCallback((event: MouseEvent) => {
|
||||
//console.log('mouse up. Contains: ' + ref.current.contains(event.target as Node));
|
||||
for (const [_idx, dataElement] of data.current) {
|
||||
if (!dataElement.ref.current) return;
|
||||
if (dataElement.ref.current.contains(event.target as Node)) {
|
||||
dataElement.mouseUpTarget = event.target as Node;
|
||||
} else {
|
||||
mouseRef.current.mouseUpTarget = null;
|
||||
dataElement.mouseUpTarget = null;
|
||||
}
|
||||
},
|
||||
[ref, mouseRef],
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (!ref.current) return;
|
||||
if (ref.current.contains(event.target as Node)) return;
|
||||
for (const [_idx, { ref }] of data.current) {
|
||||
if (!ref.current) return;
|
||||
if (ref.current.contains(event.target as Node)) return;
|
||||
}
|
||||
// context menu is fired on mouse-down so no need to do special checks.
|
||||
actionFunc();
|
||||
},
|
||||
[actionFunc, ref],
|
||||
[actionFunc],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (!ref.current) return;
|
||||
for (const [_idx, { ref }] of data.current) {
|
||||
if (!ref.current) return;
|
||||
}
|
||||
if (event.key !== 'Escape') return;
|
||||
|
||||
actionFunc();
|
||||
},
|
||||
[actionFunc, ref],
|
||||
[actionFunc],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user