@hrnec06/react_utils 1.9.3 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hrnec06/react_utils",
3
- "version": "1.9.3",
3
+ "version": "1.10.0",
4
4
  "description": "React utilities",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -0,0 +1,128 @@
1
+ import { Nullable, ReactUtils, Vector2 } from "@hrnec06/util";
2
+ import useSignal from "../../../hooks/useSignal";
3
+ import { useEffect, useState } from "react";
4
+ import useListener from "../../../hooks/useListener";
5
+ import { Debugger, useLatestRef } from "../../..";
6
+
7
+ type TooltipProps = ReactUtils.Props<{
8
+ $tooltip: React.ReactNode,
9
+ $autoGenerateWrapper?: boolean,
10
+ $hoverDelay?: number,
11
+ children: React.ReactNode
12
+ }, HTMLDivElement>;
13
+
14
+ function Tooltip({
15
+ $tooltip,
16
+ $autoGenerateWrapper = false,
17
+ $hoverDelay = 500,
18
+ ...props
19
+ }: TooltipProps)
20
+ {
21
+ const [open, setOpen] = useState<Nullable<{
22
+ position: Vector2
23
+ }>>(null);
24
+
25
+ const [hover, setHover] = useState(false);
26
+ const [position, setPosition] = useState<Vector2>([0, 0]);
27
+
28
+ const positionRef = useLatestRef(position);
29
+
30
+ useListener(window, 'mousemove', (e) => {
31
+ if (!hover) return;
32
+
33
+ setPosition([e.clientX, e.clientY]);
34
+ }, [hover]);
35
+
36
+ useEffect(() => {
37
+ if (!hover)
38
+ {
39
+ setOpen(null);
40
+ return;
41
+ }
42
+
43
+ const t = setTimeout(() => {
44
+ setOpen({
45
+ position: positionRef.current
46
+ });
47
+ }, $hoverDelay);
48
+
49
+ return () => {
50
+ clearTimeout(t);
51
+ }
52
+ }, [hover]);
53
+
54
+ const handleMouseOver = (e: React.MouseEvent<HTMLElement>) => {
55
+ if (e.isPropagationStopped())
56
+ return;
57
+
58
+ setPosition([e.clientX, e.clientY]);
59
+ setHover(true);
60
+ }
61
+
62
+ const handleMouseOut = (e: React.MouseEvent<HTMLElement>) => {
63
+ if (e.isPropagationStopped())
64
+ return;
65
+
66
+ setHover(false);
67
+ }
68
+
69
+ return (
70
+ <>
71
+ <HoverCapture
72
+ autoGenerateWrapper={$autoGenerateWrapper}
73
+ handleMouseOut={handleMouseOut}
74
+ handleMouseOver={handleMouseOver}
75
+
76
+ {...props}
77
+ >
78
+ {props.children}
79
+ </HoverCapture>
80
+
81
+ {
82
+ open && (
83
+ <div
84
+ className="fixed pointer-events-none"
85
+ style={{
86
+ left: open.position[0],
87
+ top: open.position[1]
88
+ }}
89
+ >
90
+ {$tooltip}
91
+ </div>
92
+ )
93
+ }
94
+
95
+ <Debugger.Debug value={position} />
96
+ </>
97
+ );
98
+ }
99
+
100
+ type HoverCaptureProps = ReactUtils.Props<{
101
+ autoGenerateWrapper: boolean,
102
+ handleMouseOver: (event: React.MouseEvent<HTMLElement>) => void,
103
+ handleMouseOut: (event: React.MouseEvent<HTMLElement>) => void,
104
+ }, HTMLDivElement>;
105
+
106
+ function HoverCapture({
107
+ autoGenerateWrapper,
108
+ handleMouseOver,
109
+ handleMouseOut,
110
+ ...props
111
+ }: HoverCaptureProps)
112
+ {
113
+ if (!autoGenerateWrapper)
114
+ return props.children;
115
+
116
+ return (
117
+ <div
118
+ {...props}
119
+
120
+ onMouseOver={handleMouseOver}
121
+ onMouseOut={handleMouseOut}
122
+ >
123
+ {props.children}
124
+ </div>
125
+ );
126
+ }
127
+
128
+ export default Tooltip;
@@ -0,0 +1,29 @@
1
+ import { Optional } from "@hrnec06/util";
2
+ import { createContext, useContext } from "react";
3
+ import ContextError from "../../../lib/errors/ContextError";
4
+
5
+ export interface TooltipContextExpose {
6
+ handleMouseOver: (event: React.MouseEvent<HTMLElement>) => void,
7
+ handleMouseOut: (event: React.MouseEvent<HTMLElement>) => void,
8
+ }
9
+
10
+ export interface TooltipContext {
11
+ exposed: TooltipContextExpose,
12
+ }
13
+
14
+ export const TooltipContext = createContext<Optional<TooltipContext>>(undefined);
15
+
16
+ export function useTooltipListeners(): TooltipContextExpose
17
+ {
18
+ const ctx = useTooltip();
19
+
20
+ if (!ctx)
21
+ throw new ContextError("TooltipListener");
22
+
23
+ return ctx.exposed;
24
+ }
25
+
26
+ export default function useTooltip()
27
+ {
28
+ return useContext(TooltipContext);
29
+ }
@@ -6,7 +6,7 @@ export default function useLatestRef<V>(value: V): React.RefObject<V> {
6
6
 
7
7
  useLayoutEffect(() => {
8
8
  ref.current = value;
9
- });
9
+ }, [value]);
10
10
 
11
11
  return ref;
12
12
  }
@@ -10,7 +10,11 @@ export default function useOutsideClick(
10
10
  const cb = useEvent(callback);
11
11
 
12
12
  useListener(document, 'mousedown', (event) => {
13
- if (!element || !(event.target instanceof Node) || element.contains(event.target)) return;
13
+ if (!element) return;
14
+
15
+ const realTarget = event.composedPath()[0];
16
+ if (!(realTarget instanceof Node) || element.contains(realTarget))
17
+ return;
14
18
 
15
19
  cb(event);
16
20
  }, [cb, element]);
package/src/index.ts CHANGED
@@ -30,6 +30,8 @@ import ContextMenu from "./components/ContextMenu/ContextMenu";
30
30
 
31
31
  import ShadowRoot from "./components/ShadowRoot/ShadowRoot";
32
32
 
33
+ import Tooltip from "./components/UI/Tooltip/Tooltip";
34
+
33
35
  import * as util from './lib/utils';
34
36
  import ContextError from "./lib/errors/ContextError";
35
37
 
@@ -68,7 +70,10 @@ export {
68
70
  Debugger,
69
71
  ResizeableBox,
70
72
  Dialog,
71
- ContextMenu,
73
+ ContextMenu, // should go to UI too
74
+
75
+ // UI
76
+ Tooltip,
72
77
 
73
78
  // Shadow root
74
79
  ShadowRoot,