@hrnec06/react_utils 1.2.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hrnec06/react_utils",
3
- "version": "1.2.3",
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.14",
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",
@@ -19,7 +19,20 @@ export default function ParseValueSimple({
19
19
  const numStr = value.toString();
20
20
 
21
21
  if (numStr.length > MAX_LENGTH)
22
- return <ValueKeyword value={typeof value} />;
22
+ {
23
+ const parts = numStr.split('.');
24
+
25
+ if (parts[0].length > MAX_LENGTH || parts.length < 2)
26
+ return <ValueKeyword value={typeof value} />;
27
+
28
+
29
+ let decimals, short = parts[0];
30
+
31
+ if ((decimals = parts[1].substring(0, MAX_LENGTH - short.length - 1)) && decimals.length > 0)
32
+ short += '.' + decimals;
33
+
34
+ return <ValueNumber value={short} />;
35
+ }
23
36
 
24
37
  return <ValueNumber value={value} />
25
38
  }
@@ -17,7 +17,7 @@ export default function ValueFunction({
17
17
  <span className="text-[#f2824a]">
18
18
  {'ƒ '}
19
19
  </span>
20
- <span>
20
+ <span className="text-white">
21
21
  {`${value.name}(${args})`}
22
22
  </span>
23
23
  </>
@@ -1,5 +1,5 @@
1
1
  interface ValueNumberProps {
2
- value: number | bigint
2
+ value: number | bigint | string
3
3
  }
4
4
  export default function ValueNumber({
5
5
  value
@@ -11,7 +11,7 @@ export default function ValueSymbol({
11
11
  <>
12
12
  <ValueKeyword value="Symbol" />
13
13
  <Char_Bracket text="(" />
14
- <span>
14
+ <span className="text-white">
15
15
  {value.description}
16
16
  </span>
17
17
  <Char_Bracket text=")" />
@@ -19,7 +19,7 @@ export class Signal<T>
19
19
  {
20
20
  }
21
21
 
22
- public set value(value: T)
22
+ public set value(value: T | ((prev: T) => T))
23
23
  {
24
24
  this.setState(value);
25
25
  }
@@ -28,4 +28,5 @@ export class Signal<T>
28
28
  {
29
29
  return this._value;
30
30
  }
31
+
31
32
  }
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
+ }