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