@hrnec06/react_utils 1.2.4 → 1.2.6
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 +2 -2
- package/src/debugger/parser/DebugParserSimple.tsx +1 -1
- package/src/debugger/parser/ValueFunction.tsx +1 -1
- package/src/debugger/parser/ValueNumber.tsx +1 -1
- package/src/debugger/parser/ValueSymbol.tsx +1 -1
- package/src/index.tsx +4 -1
- package/src/ui/ResizeableBox.tsx +342 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hrnec06/react_utils",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "A debugger component for react.",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.tsx"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"typescript": "^5.8.3"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@hrnec06/util": "^1.4.
|
|
29
|
+
"@hrnec06/util": "^1.4.21",
|
|
30
30
|
"clsx": "^2.1.1",
|
|
31
31
|
"lucide-react": "^0.525.0",
|
|
32
32
|
"react": "^19.1.0",
|
|
@@ -31,7 +31,7 @@ export default function ParseValueSimple({
|
|
|
31
31
|
if ((decimals = parts[1].substring(0, MAX_LENGTH - short.length - 1)) && decimals.length > 0)
|
|
32
32
|
short += '.' + decimals;
|
|
33
33
|
|
|
34
|
-
return <
|
|
34
|
+
return <ValueNumber value={short} />;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
return <ValueNumber value={value} />
|
package/src/index.tsx
CHANGED
|
@@ -4,7 +4,9 @@ import useUpdatedRef from "./hooks/useUpdatedRef";
|
|
|
4
4
|
import useUpdateEffect from "./hooks/useUpdateEffect";
|
|
5
5
|
import useWindowSize from "./hooks/useWindowSize";
|
|
6
6
|
import useSignal from "./hooks/useSignal";
|
|
7
|
+
|
|
7
8
|
import Debugger from './debugger/Debugger';
|
|
9
|
+
import ResizeableBox from "./ui/ResizeableBox";
|
|
8
10
|
|
|
9
11
|
export {
|
|
10
12
|
useKeyListener,
|
|
@@ -13,5 +15,6 @@ export {
|
|
|
13
15
|
useUpdateEffect,
|
|
14
16
|
useWindowSize,
|
|
15
17
|
useSignal,
|
|
16
|
-
Debugger
|
|
18
|
+
Debugger,
|
|
19
|
+
ResizeableBox
|
|
17
20
|
};
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { addVector2, cloneVector2, match, matchBool, matchMouseEvent, MouseButton, Nullable, Optional, Vector2 } from "@hrnec06/util";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
4
|
+
import useSignal, { Signal } from "../hooks/useSignal";
|
|
5
|
+
import useListener from "../hooks/useListener";
|
|
6
|
+
import { useUpdatedRef } from "..";
|
|
7
|
+
|
|
8
|
+
// Type definitions
|
|
9
|
+
type DragAreaSize = 'small' | 'medium' | 'big';
|
|
10
|
+
type DragSide = 'top' | 'right' | 'bottom' | 'left';
|
|
11
|
+
type ResizeEvent = (size: Vector2, offset: Vector2) => void;
|
|
12
|
+
type StartEvent = () => void;
|
|
13
|
+
type EndEvent = () => void;
|
|
14
|
+
|
|
15
|
+
type RepositionMode = boolean | 'x' | 'y';
|
|
16
|
+
type MouseMoveHandler = (offset: Vector2) => Vector2
|
|
17
|
+
|
|
18
|
+
interface DragState {
|
|
19
|
+
originalSize?: Vector2,
|
|
20
|
+
originalOffset?: Vector2,
|
|
21
|
+
|
|
22
|
+
dragOrigin: Vector2,
|
|
23
|
+
applyReposition: RepositionMode,
|
|
24
|
+
mouseMove: MouseMoveHandler
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Context
|
|
28
|
+
interface ResizeableBoxContext {
|
|
29
|
+
size?: Vector2,
|
|
30
|
+
offset?: Vector2,
|
|
31
|
+
|
|
32
|
+
options: {
|
|
33
|
+
insetAreas: boolean,
|
|
34
|
+
dragAreaSize: DragAreaSize,
|
|
35
|
+
allowedSides: DragSide[],
|
|
36
|
+
allowCorners: boolean | Vector2<DragSide>[],
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
drag: Signal<Nullable<DragState>>,
|
|
40
|
+
}
|
|
41
|
+
const ResizeableBoxContext = createContext<Optional<ResizeableBoxContext>>(undefined);
|
|
42
|
+
|
|
43
|
+
function useResizeableBox()
|
|
44
|
+
{
|
|
45
|
+
const ctx = useContext(ResizeableBoxContext);
|
|
46
|
+
|
|
47
|
+
if (!ctx)
|
|
48
|
+
throw new Error("useResizeableBox may be used only within ResizeableBoxContext.Provider!");
|
|
49
|
+
|
|
50
|
+
return ctx;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Component
|
|
54
|
+
|
|
55
|
+
interface ResizeableBoxProps extends React.HTMLProps<HTMLDivElement>
|
|
56
|
+
{
|
|
57
|
+
$size?: Vector2,
|
|
58
|
+
$offset?: Vector2,
|
|
59
|
+
|
|
60
|
+
$dragAreaSize?: DragAreaSize,
|
|
61
|
+
$allowedSides?: DragSide[],
|
|
62
|
+
$allowCorners?: boolean | Vector2<DragSide>[],
|
|
63
|
+
$insetAreas?: boolean,
|
|
64
|
+
|
|
65
|
+
$onResize?: ResizeEvent,
|
|
66
|
+
$onStart?: StartEvent,
|
|
67
|
+
$onEnd?: EndEvent,
|
|
68
|
+
}
|
|
69
|
+
export default function ResizeableBox({
|
|
70
|
+
$size,
|
|
71
|
+
$offset,
|
|
72
|
+
|
|
73
|
+
$dragAreaSize = "medium",
|
|
74
|
+
$allowedSides = ['top', 'right', 'bottom', 'left'],
|
|
75
|
+
$allowCorners = true,
|
|
76
|
+
$insetAreas = true,
|
|
77
|
+
|
|
78
|
+
$onResize,
|
|
79
|
+
$onStart,
|
|
80
|
+
$onEnd,
|
|
81
|
+
|
|
82
|
+
...props
|
|
83
|
+
}: ResizeableBoxProps)
|
|
84
|
+
{
|
|
85
|
+
const drag = useSignal<Nullable<DragState>>(null);
|
|
86
|
+
|
|
87
|
+
const onResizeRef = useUpdatedRef($onResize);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (drag.value === null)
|
|
91
|
+
$onEnd && $onEnd();
|
|
92
|
+
else
|
|
93
|
+
$onStart && $onStart();
|
|
94
|
+
}, [drag.value]);
|
|
95
|
+
|
|
96
|
+
useListener(document, 'mouseup', (e) => {
|
|
97
|
+
if (drag.value === null || !matchMouseEvent(e, MouseButton.Left))
|
|
98
|
+
return;
|
|
99
|
+
|
|
100
|
+
drag.value = null;
|
|
101
|
+
}, [drag.value === null]);
|
|
102
|
+
|
|
103
|
+
useListener(document, 'mousemove', (e) => {
|
|
104
|
+
if (drag.value === null || !onResizeRef.current)
|
|
105
|
+
return;
|
|
106
|
+
|
|
107
|
+
const offset: Vector2 = [
|
|
108
|
+
e.clientX - drag.value.dragOrigin[0],
|
|
109
|
+
e.clientY - drag.value.dragOrigin[1],
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const sizeOffset = drag.value.mouseMove(offset);
|
|
113
|
+
const positionOffset: Vector2 = [0, 0];
|
|
114
|
+
|
|
115
|
+
if (drag.value.applyReposition !== false)
|
|
116
|
+
{
|
|
117
|
+
const offset: Vector2 = cloneVector2(sizeOffset);
|
|
118
|
+
|
|
119
|
+
positionOffset[0] = (drag.value.applyReposition === true || drag.value.applyReposition === 'x') ? -offset[0] : 0;
|
|
120
|
+
positionOffset[1] = (drag.value.applyReposition === true || drag.value.applyReposition === 'y') ? -offset[1] : 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (drag.value.originalSize)
|
|
124
|
+
addVector2(sizeOffset, drag.value.originalSize);
|
|
125
|
+
|
|
126
|
+
if (drag.value.originalOffset)
|
|
127
|
+
addVector2(positionOffset, drag.value.originalOffset);
|
|
128
|
+
|
|
129
|
+
onResizeRef.current(sizeOffset, positionOffset);
|
|
130
|
+
}, [drag.value === null]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<ResizeableBoxContext.Provider
|
|
134
|
+
value={{
|
|
135
|
+
size: $size,
|
|
136
|
+
offset: $offset,
|
|
137
|
+
|
|
138
|
+
options: {
|
|
139
|
+
dragAreaSize: $dragAreaSize,
|
|
140
|
+
allowedSides: $allowedSides,
|
|
141
|
+
allowCorners: $allowCorners,
|
|
142
|
+
insetAreas: $insetAreas,
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
drag: drag
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<div
|
|
149
|
+
{...props}
|
|
150
|
+
|
|
151
|
+
className={clsx(
|
|
152
|
+
props.className,
|
|
153
|
+
"relative w-full h-full"
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
<DragAreas />
|
|
157
|
+
|
|
158
|
+
{props.children}
|
|
159
|
+
</div>
|
|
160
|
+
</ResizeableBoxContext.Provider>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface DragAreasProps {
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
function DragAreas({
|
|
168
|
+
|
|
169
|
+
}: DragAreasProps)
|
|
170
|
+
{
|
|
171
|
+
return (
|
|
172
|
+
<>
|
|
173
|
+
<DragArea
|
|
174
|
+
spanHorizontal
|
|
175
|
+
top
|
|
176
|
+
|
|
177
|
+
applyReposition={'y'}
|
|
178
|
+
onMove={([, y]) => [0, -y]}
|
|
179
|
+
/>
|
|
180
|
+
|
|
181
|
+
<DragArea
|
|
182
|
+
spanVertical
|
|
183
|
+
right
|
|
184
|
+
|
|
185
|
+
onMove={([x]) => [x, 0]}
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
<DragArea
|
|
189
|
+
spanHorizontal
|
|
190
|
+
bottom
|
|
191
|
+
|
|
192
|
+
onMove={([, y]) => [0, y]}
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
<DragArea
|
|
196
|
+
spanVertical
|
|
197
|
+
left
|
|
198
|
+
|
|
199
|
+
applyReposition={'x'}
|
|
200
|
+
onMove={([x]) => [-x, 0]}
|
|
201
|
+
/>
|
|
202
|
+
|
|
203
|
+
{/* Corners */}
|
|
204
|
+
|
|
205
|
+
<DragArea
|
|
206
|
+
isCorner
|
|
207
|
+
left
|
|
208
|
+
top
|
|
209
|
+
|
|
210
|
+
applyReposition={true}
|
|
211
|
+
onMove={([x, y]) => [-x, -y]}
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
<DragArea
|
|
215
|
+
isCorner
|
|
216
|
+
right
|
|
217
|
+
top
|
|
218
|
+
|
|
219
|
+
applyReposition={'y'}
|
|
220
|
+
onMove={([x, y]) => [x, -y]}
|
|
221
|
+
/>
|
|
222
|
+
|
|
223
|
+
<DragArea
|
|
224
|
+
isCorner
|
|
225
|
+
left
|
|
226
|
+
bottom
|
|
227
|
+
|
|
228
|
+
applyReposition={'x'}
|
|
229
|
+
onMove={([x, y]) => [-x, y]}
|
|
230
|
+
/>
|
|
231
|
+
|
|
232
|
+
<DragArea
|
|
233
|
+
isCorner
|
|
234
|
+
right
|
|
235
|
+
bottom
|
|
236
|
+
|
|
237
|
+
onMove={([x, y]) => [x, y]}
|
|
238
|
+
/>
|
|
239
|
+
</>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interface DragAreaProps {
|
|
244
|
+
isCorner?: boolean,
|
|
245
|
+
|
|
246
|
+
spanVertical?: boolean,
|
|
247
|
+
spanHorizontal?: boolean,
|
|
248
|
+
|
|
249
|
+
onMove: (offset: Vector2) => Vector2,
|
|
250
|
+
applyReposition?: RepositionMode,
|
|
251
|
+
|
|
252
|
+
top?: boolean,
|
|
253
|
+
right?: boolean,
|
|
254
|
+
bottom?: boolean,
|
|
255
|
+
left?: boolean
|
|
256
|
+
}
|
|
257
|
+
function DragArea({
|
|
258
|
+
isCorner,
|
|
259
|
+
|
|
260
|
+
spanVertical,
|
|
261
|
+
spanHorizontal,
|
|
262
|
+
|
|
263
|
+
onMove,
|
|
264
|
+
applyReposition = false,
|
|
265
|
+
|
|
266
|
+
...enabledSides
|
|
267
|
+
}: DragAreaProps)
|
|
268
|
+
{
|
|
269
|
+
const box = useResizeableBox();
|
|
270
|
+
|
|
271
|
+
const isAllowed = (side: DragSide) => box.options.allowedSides.includes(side);
|
|
272
|
+
|
|
273
|
+
if (isCorner && !box.options.allowCorners)
|
|
274
|
+
return undefined;
|
|
275
|
+
|
|
276
|
+
if (
|
|
277
|
+
(enabledSides.top && !isAllowed('top')) ||
|
|
278
|
+
(enabledSides.right && !isAllowed('right')) ||
|
|
279
|
+
(enabledSides.bottom && !isAllowed('bottom')) ||
|
|
280
|
+
(enabledSides.left && !isAllowed('left'))
|
|
281
|
+
)
|
|
282
|
+
return undefined;
|
|
283
|
+
|
|
284
|
+
if (isCorner && Array.isArray(box.options.allowCorners))
|
|
285
|
+
{
|
|
286
|
+
if (!box.options.allowCorners.some((combo) => enabledSides[combo[0]] && enabledSides[combo[1]]))
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
291
|
+
if (box.drag.value !== null || !matchMouseEvent(e.nativeEvent, MouseButton.Left))
|
|
292
|
+
return;
|
|
293
|
+
|
|
294
|
+
box.drag.value = {
|
|
295
|
+
originalSize: box.size,
|
|
296
|
+
originalOffset: box.offset,
|
|
297
|
+
dragOrigin: [e.clientX, e.clientY],
|
|
298
|
+
mouseMove: onMove,
|
|
299
|
+
applyReposition: applyReposition,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<div
|
|
305
|
+
onMouseDown={handleMouseDown}
|
|
306
|
+
|
|
307
|
+
className={clsx(
|
|
308
|
+
"absolute",
|
|
309
|
+
"select-none",
|
|
310
|
+
|
|
311
|
+
spanVertical ? 'h-full' : clsx(
|
|
312
|
+
box.options.dragAreaSize === 'small' && 'h-2',
|
|
313
|
+
box.options.dragAreaSize === 'medium' && 'h-4',
|
|
314
|
+
box.options.dragAreaSize === 'big' && 'h-6'
|
|
315
|
+
),
|
|
316
|
+
|
|
317
|
+
spanHorizontal ? 'w-full' : clsx(
|
|
318
|
+
box.options.dragAreaSize === 'small' && 'w-2',
|
|
319
|
+
box.options.dragAreaSize === 'medium' && 'w-4',
|
|
320
|
+
box.options.dragAreaSize === 'big' && 'w-6'
|
|
321
|
+
),
|
|
322
|
+
|
|
323
|
+
enabledSides.top && clsx('bottom-full', box.options.insetAreas && 'translate-y-1/2'),
|
|
324
|
+
enabledSides.right && clsx('left-full', box.options.insetAreas && '-translate-x-1/2'),
|
|
325
|
+
enabledSides.bottom && clsx('top-full', box.options.insetAreas && '-translate-y-1/2'),
|
|
326
|
+
enabledSides.left && clsx('right-full', box.options.insetAreas && 'translate-x-1/2'),
|
|
327
|
+
|
|
328
|
+
matchBool({
|
|
329
|
+
"cursor-nw-resize": enabledSides.top && enabledSides.left,
|
|
330
|
+
"cursor-ne-resize": enabledSides.top && enabledSides.right,
|
|
331
|
+
"cursor-se-resize": enabledSides.bottom && enabledSides.right,
|
|
332
|
+
"cursor-sw-resize": enabledSides.bottom && enabledSides.left,
|
|
333
|
+
|
|
334
|
+
"cursor-n-resize": enabledSides.top,
|
|
335
|
+
"cursor-e-resize": enabledSides.right,
|
|
336
|
+
"cursor-s-resize": enabledSides.bottom,
|
|
337
|
+
"cursor-w-resize": enabledSides.left
|
|
338
|
+
}),
|
|
339
|
+
)}
|
|
340
|
+
/>
|
|
341
|
+
);
|
|
342
|
+
}
|