mirror of
https://github.com/captbaritone/webamp.git
synced 2026-08-01 22:45:18 +00:00
Use hooks for ContextMenuTarget
This commit is contained in:
parent
3df608a665
commit
3dc8dc5af6
2 changed files with 65 additions and 70 deletions
|
|
@ -98,6 +98,7 @@ interface ContextMenuProps {
|
|||
offsetLeft: number;
|
||||
top?: boolean;
|
||||
bottom?: boolean;
|
||||
// TODO: Remove this. Just conditionally render in the parent.
|
||||
selected: boolean;
|
||||
zIndex: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
// @ts-ignore #hook-types
|
||||
import React, { useState, useRef, useEffect, useMemo } from "react";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
|
||||
type DivProps = React.DetailedHTMLProps<
|
||||
|
|
@ -15,75 +16,68 @@ interface State {
|
|||
selected: boolean;
|
||||
}
|
||||
|
||||
export default class ContextMenuTarget extends React.Component<Props, State> {
|
||||
handleNode?: HTMLDivElement | null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { selected: false };
|
||||
function getNodeOffset(node?: Element) {
|
||||
if (!node) {
|
||||
return { top: 0, left: 0 };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("click", this._handleGlobalClick);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("click", this._handleGlobalClick);
|
||||
}
|
||||
|
||||
_handleHandleClick = () => {
|
||||
this.setState({ selected: !this.state.selected });
|
||||
};
|
||||
|
||||
_handleGlobalClick = (e: MouseEvent) => {
|
||||
if (
|
||||
// Typescript does not believe that these click events are always fired on DOM nodes.
|
||||
e.target instanceof Element &&
|
||||
this.state.selected &&
|
||||
// Not sure how, but it's possible for this to get called when handleNode is null/undefined.
|
||||
// https://sentry.io/share/issue/2066cd79f21e4f279791319f4d2ea35d/
|
||||
this.handleNode &&
|
||||
!this.handleNode.contains(e.target!)
|
||||
) {
|
||||
this.setState({ selected: false });
|
||||
}
|
||||
};
|
||||
|
||||
_offset() {
|
||||
if (!this.handleNode) {
|
||||
return { top: 0, left: 0 };
|
||||
}
|
||||
|
||||
const rect = this.handleNode.getBoundingClientRect();
|
||||
const scrollLeft =
|
||||
window.pageXOffset || document.documentElement!.scrollLeft;
|
||||
const scrollTop = window.pageYOffset || document.documentElement!.scrollTop;
|
||||
return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { handle, children, top, bottom, ...passThroughProps } = this.props;
|
||||
const offset = this._offset();
|
||||
return (
|
||||
<div {...passThroughProps}>
|
||||
<div
|
||||
className="handle"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
ref={node => (this.handleNode = node)}
|
||||
onClick={this._handleHandleClick}
|
||||
>
|
||||
{handle}
|
||||
</div>
|
||||
<ContextMenu
|
||||
selected={this.state.selected}
|
||||
offsetTop={offset.top}
|
||||
offsetLeft={offset.left}
|
||||
top={top}
|
||||
bottom={bottom}
|
||||
>
|
||||
{children}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const rect = node.getBoundingClientRect();
|
||||
const scrollLeft = window.pageXOffset || document.documentElement!.scrollLeft;
|
||||
const scrollTop = window.pageYOffset || document.documentElement!.scrollTop;
|
||||
return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
|
||||
}
|
||||
|
||||
function ContextMenuTarget(props: Props) {
|
||||
const handleNode = useRef(null);
|
||||
const [selected, setSelected] = useState(false);
|
||||
useEffect(
|
||||
() => {
|
||||
function handleGlobalClick(e: MouseEvent) {
|
||||
if (
|
||||
selected &&
|
||||
// Typescript does not believe that these click events are always fired on DOM nodes.
|
||||
e.target instanceof Element &&
|
||||
selected &&
|
||||
// Not sure how, but it's possible for this to get called when handleNode is null/undefined.
|
||||
// https://sentry.io/share/issue/2066cd79f21e4f279791319f4d2ea35d/
|
||||
handleNode.current &&
|
||||
!handleNode.current.contains(e.target!)
|
||||
) {
|
||||
setSelected(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("click", handleGlobalClick);
|
||||
return () => {
|
||||
document.removeEventListener("click", handleGlobalClick);
|
||||
};
|
||||
},
|
||||
[selected, handleNode.current]
|
||||
);
|
||||
|
||||
const offset = useMemo(() => getNodeOffset(handleNode.current), [selected]);
|
||||
|
||||
const { handle, children, top, bottom, ...passThroughProps } = props;
|
||||
return (
|
||||
<div {...passThroughProps}>
|
||||
<div
|
||||
className="handle"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
ref={handleNode}
|
||||
onClick={() => setSelected(!selected)}
|
||||
>
|
||||
{handle}
|
||||
</div>
|
||||
<ContextMenu
|
||||
selected={selected}
|
||||
offsetTop={offset.top}
|
||||
offsetLeft={offset.left}
|
||||
top={top}
|
||||
bottom={bottom}
|
||||
>
|
||||
{children}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContextMenuTarget;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue