@hrnec06/react_utils 1.5.1 → 1.7.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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/components/ContextMenu/ContextMenu.tsx +235 -0
  3. package/src/components/ContextMenu/ContextMenu.types.ts +17 -0
  4. package/src/components/ContextMenu/ContextMenuCtx.tsx +33 -0
  5. package/src/components/ContextMenu/ContextMenuItem.tsx +147 -0
  6. package/src/components/ContextMenu/ContextMenuRoot.tsx +28 -0
  7. package/src/components/ContextMenu/ContextMenuSection.tsx +28 -0
  8. package/src/components/Debugger/Debugger.tsx +86 -84
  9. package/src/components/Debugger/DebuggerTerminal.tsx +18 -49
  10. package/src/components/Debugger/parser/DebugParser.tsx +121 -14
  11. package/src/components/Debugger/parser/DebugTerminal.tsx +24 -7
  12. package/src/components/Debugger/parser/ValueArray.tsx +22 -7
  13. package/src/components/Debugger/parser/ValueBoolean.tsx +15 -4
  14. package/src/components/Debugger/parser/ValueConstant.tsx +21 -0
  15. package/src/components/Debugger/parser/ValueFunction.tsx +17 -5
  16. package/src/components/Debugger/parser/ValueNumber.tsx +14 -4
  17. package/src/components/Debugger/parser/ValueObject.tsx +61 -17
  18. package/src/components/Debugger/parser/ValueString.tsx +13 -4
  19. package/src/components/ResizeableBox/ResizeableBox.tsx +1 -1
  20. package/src/hooks/useDebounce.ts +26 -0
  21. package/src/hooks/useDefaultValue.ts +8 -0
  22. package/src/hooks/useEfficientRef.ts +3 -2
  23. package/src/hooks/useEvent.ts +15 -0
  24. package/src/hooks/useLatestRef.ts +12 -0
  25. package/src/hooks/useLocalStorage.ts +134 -0
  26. package/src/hooks/useSyncRef.ts +17 -0
  27. package/src/hooks/useTransition.ts +2 -1
  28. package/src/index.ts +16 -5
  29. package/src/lib/errors/ContextError.ts +11 -0
  30. package/src/hooks/useEfficientState.ts +0 -9
  31. package/src/hooks/useUpdatedRef.ts +0 -12
@@ -1,4 +1,4 @@
1
- import { minmax, Nullable, Vector2 } from "@hrnec06/util";
1
+ import { matchMouseEvent, minmax, MouseButton, Nullable, Vector2 } from "@hrnec06/util";
2
2
  import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { createPortal } from 'react-dom';
4
4
  import {DebuggerContext} from "./DebuggerContext";
@@ -6,12 +6,49 @@ import clsx from "clsx";
6
6
  import ScrollBar from "./DebuggerScrollBar";
7
7
  import ParseValue from "./parser/DebugParser";
8
8
  import useKeyListener from "../../hooks/useKeyListener";
9
- import useUpdateEffect from "../../hooks/useUpdateEffect";
10
- import useUpdatedRef from "../../hooks/useUpdatedRef";
9
+ import useUpdatedRef from "../../hooks/useLatestRef";
11
10
  import ResizeableBox from "../ResizeableBox/ResizeableBox";
11
+ import useNamespacedId from "../../hooks/useNamespacedId";
12
+ import useLocalStorage from "../../hooks/useLocalStorage";
13
+ import z from "zod";
14
+ import useDefaultValue from "../../hooks/useDefaultValue";
15
+
16
+ const DEBUG_NAMESPACE = "debugger";
17
+
18
+ function useDebuggerID(_customID?: string): string
19
+ {
20
+ const customID = useDefaultValue(_customID);
21
+
22
+ if (customID)
23
+ return customID;
24
+
25
+ return useNamespacedId(DEBUG_NAMESPACE);
26
+ }
27
+
28
+ interface DebuggerLabelProps {
29
+ label: string
30
+ }
31
+ function DebuggerLabel({ label }: DebuggerLabelProps)
32
+ {
33
+ return (
34
+ <div
35
+ className={clsx(
36
+ "px-3 py-1",
37
+ "text font-semibold text-white bg-[#181818]"
38
+ )}
39
+ >
40
+ {label}
41
+ </div>
42
+ )
43
+ }
12
44
 
13
45
  interface DebugProps {
14
46
  value: unknown,
47
+ /**
48
+ * Assign a persistant ID if saved position breaks because of remounts.
49
+ */
50
+ id?: string,
51
+ label?: string,
15
52
  openPaths?: string[],
16
53
  excludePaths?: string[],
17
54
  size?: 'normal' | 'big' | 'tiny',
@@ -21,6 +58,8 @@ interface DebugProps {
21
58
  }
22
59
  function Debug({
23
60
  value,
61
+ id: customID,
62
+ label,
24
63
  openPaths,
25
64
  excludePaths,
26
65
  size = 'normal',
@@ -29,24 +68,28 @@ function Debug({
29
68
  openRoot = true
30
69
  }: DebugProps) {
31
70
  // Config
32
- const LS_POS_KEY = 'debugger_position';
33
- const LS_SIZE_KEY = 'debugger_size';
34
- const LS_EXPAND_KEY = 'debugger_expanded';
71
+
72
+ const id = useDebuggerID(customID);
73
+
74
+ const { LS_POS_KEY, LS_SIZE_KEY, LS_EXPAND_KEY } = useMemo(() => ({
75
+ LS_POS_KEY: id + ".position",
76
+ LS_SIZE_KEY: id + ".size",
77
+ LS_EXPAND_KEY: id + ".expanded"
78
+ }), [id]);
35
79
 
36
80
  // Keybinds
37
81
  const f9_pressed = useKeyListener('F9');
38
82
 
39
- // Position
40
- const [position, setPosition] = useState<Vector2>([0, 0]);
83
+ // Position and resize
84
+ const [position, setPosition] = useLocalStorage<Vector2>(LS_POS_KEY, [0, 0], z.tuple([z.number(), z.number()]));
85
+ const [windowSize, setWindowSize] = useLocalStorage<Vector2>(LS_SIZE_KEY, [500, 500], z.tuple([z.number(), z.number()]));
86
+
41
87
  const [grab, setGrab] = useState<Nullable<{
42
88
  windowOrigin: Vector2,
43
89
  positionOrigin: Vector2
44
90
  }>>(null);
45
91
  const [grabOffset, setGrabOffset] = useState<Nullable<Vector2>>(null);
46
92
 
47
- // Resize
48
- const [windowSize, setWindowSize] = useState<Vector2>([500, 500]);
49
-
50
93
  // Scroll
51
94
  const [scrollHeight, setScrollHeight] = useState(0);
52
95
  const [containerHeight, setContainerHeight] = useState(0);
@@ -98,64 +141,6 @@ function Debug({
98
141
 
99
142
  const isScrollable = useMemo(() => scrollHeight > 0, [scrollHeight]);
100
143
 
101
- // Load saved position
102
- useEffect(() => {
103
- try {
104
- const saved_position = localStorage.getItem(LS_POS_KEY);
105
- if (saved_position) {
106
- const [v1, v2] = saved_position.split(',');
107
-
108
- if (v1 === undefined || v2 === undefined)
109
- throw new Error('Invalid vector: ' + saved_position);
110
-
111
- const [v1_p, v2_p] = [parseInt(v1), parseInt(v2)];
112
-
113
- if (isNaN(v1_p) || isNaN(v2_p))
114
- throw new Error('Invalid vector values: ' + saved_position);
115
-
116
- setPosition([v1_p, v2_p]);
117
- }
118
- } catch (error) {
119
- console.error(`Error loading saved position: `, error);
120
-
121
- localStorage.removeItem(LS_POS_KEY);
122
- }
123
- }, []);
124
-
125
- // Load saved size
126
- useEffect(() => {
127
- try {
128
- const saved_size = localStorage.getItem(LS_SIZE_KEY);
129
- if (saved_size) {
130
- const [v1, v2] = saved_size.split(',');
131
-
132
- if (v1 === undefined || v2 === undefined)
133
- throw new Error('Invalid vector: ' + saved_size);
134
-
135
- const [v1_p, v2_p] = [parseInt(v1), parseInt(v2)];
136
-
137
- if (isNaN(v1_p) || isNaN(v2_p))
138
- throw new Error('Invalid vector values: ' + saved_size);
139
-
140
- setWindowSize([v1_p, v2_p]);
141
- }
142
- } catch (error) {
143
- console.error(`Error loading saved size: `, error);
144
-
145
- localStorage.removeItem(LS_POS_KEY);
146
- }
147
- }, []);
148
-
149
- // Save saved position
150
- useUpdateEffect(() => {
151
- localStorage.setItem(LS_POS_KEY, `${position[0]},${position[1]}`);
152
- }, [position]);
153
-
154
- // Save saved size
155
- useUpdateEffect(() => {
156
- localStorage.setItem(LS_SIZE_KEY, `${windowSize[0]},${windowSize[1]}`);
157
- }, [windowSize]);
158
-
159
144
  // Reset position
160
145
  useEffect(() => {
161
146
  if (!f9_pressed)
@@ -196,6 +181,8 @@ function Debug({
196
181
 
197
182
  // Handlers
198
183
  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
184
+ if (!matchMouseEvent(e.nativeEvent, MouseButton.Left)) return;
185
+
199
186
  e.preventDefault();
200
187
 
201
188
  setGrab({
@@ -263,7 +250,7 @@ function Debug({
263
250
  }
264
251
  }}
265
252
  >
266
- <div className="fixed pointer-events-none w-full h-full left-0 top-0 overflow-hidden z-999999">
253
+ <div className="fixed pointer-events-none w-full h-full left-0 top-0 overflow-hidden z-500">
267
254
  <div
268
255
  className={clsx(
269
256
  "absolute font-jetbrains pointer-events-auto",
@@ -285,25 +272,40 @@ function Debug({
285
272
  $onResize={handleResize}
286
273
  >
287
274
  <div
288
- onWheel={handleWheel}
289
- className="bg-[#1f1f1f] shadow-lg rounded-md border border-[#4b4b4b] w-full h-full overflow-hidden flex"
275
+ // onMouseDown={handleMouseDown}
276
+ className={clsx(
277
+ "flex flex-col w-full h-full",
278
+ "bg-[#1f1f1f] border border-[#4b4b4b] shadow-lg rounded-md"
279
+ )}
290
280
  >
281
+ {label && (
282
+ <DebuggerLabel
283
+ label={label}
284
+ />
285
+ )}
286
+
291
287
  <div
292
- ref={containerRef}
293
- onMouseDown={handleMouseDown}
294
- className=" cursor-grab w-full"
295
- style={{
296
- transform: `translateY(${-(scrollProgress * scrollHeight)}px)`
297
- }}
288
+ onWheel={handleWheel}
289
+ className="w-full h-full overflow-hidden flex"
298
290
  >
299
- <div className="p-3 pl-5">
300
- <ParseValue value={value} />
291
+ <div
292
+ ref={containerRef}
293
+ onMouseDown={handleMouseDown}
294
+ className="cursor-grab w-full"
295
+ style={{
296
+ transform: `translateY(${-(scrollProgress * scrollHeight)}px)`
297
+ }}
298
+ >
299
+ <div className="p-3 pl-5">
300
+ <ParseValue value={value} />
301
+ </div>
301
302
  </div>
303
+ <ScrollBar
304
+ containerHeight={containerHeight}
305
+ scrollHeight={scrollHeight}
306
+ />
302
307
  </div>
303
- <ScrollBar
304
- containerHeight={containerHeight}
305
- scrollHeight={scrollHeight}
306
- />
308
+
307
309
  </div>
308
310
  </ResizeableBox>
309
311
  </div>
@@ -3,6 +3,7 @@ import { assert, Optional, ReactUtils } from "@hrnec06/util";
3
3
  import useUpdateEffect from "../../hooks/useUpdateEffect";
4
4
  import useNamespacedId from "../../hooks/useNamespacedId";
5
5
  import z from "zod";
6
+ import useLocalStorage from "../../hooks/useLocalStorage";
6
7
 
7
8
  const TERMINAL_NAMESPACE = "terminal";
8
9
 
@@ -73,11 +74,6 @@ class Terminal
73
74
  this.history = history;
74
75
  }
75
76
 
76
- public getLsKey(): string
77
- {
78
- return "terminal." + this.id;
79
- }
80
-
81
77
  public clear(): Terminal
82
78
  {
83
79
  this.history[1]([]);
@@ -214,37 +210,6 @@ function TerminalContextProvider({
214
210
  </TerminalContext.Provider>
215
211
  )
216
212
  }
217
-
218
- /**
219
- * Returns the default history for a terminal
220
- */
221
- function getDefaultHistory(terminal: Terminal)
222
- {
223
- try
224
- {
225
- const savedValue = localStorage.getItem(terminal.getLsKey());
226
-
227
- const savedValueJSON = JSON.parse(savedValue ?? "[]");
228
-
229
- const schema = z.array(z.object({
230
- value: z.any(),
231
- role: z.enum(LogRole),
232
- type: z.enum(LogType)
233
- }));
234
-
235
- const parsed = schema.parse(savedValueJSON);
236
-
237
- return parsed;
238
- }
239
- catch (error)
240
- {
241
- localStorage.removeItem(terminal.getLsKey());
242
- console.error("Terminal data parse failed: ", error);
243
-
244
- return [];
245
- }
246
- }
247
-
248
213
  /**
249
214
  * Recursively browses the contexts to find a matching terminal
250
215
  */
@@ -304,22 +269,26 @@ function useTerminal(
304
269
  {
305
270
  const id = useNamespacedId(TERMINAL_NAMESPACE);
306
271
 
307
- const history = useState<LogItem[]>([]);
308
- const inputHistory = useState<string[]>([]);
309
-
310
- const terminal = useMemo(() => {
311
- return new Terminal(id, name, history, inputHistory, onInput);
312
- }, [name, history, onInput]);
272
+ const history = useLocalStorage<LogItem[]>(
273
+ "terminal." + id,
274
+ [],
275
+ z.array(z.object({
276
+ value: z.any(),
277
+ role: z.enum(LogRole),
278
+ type: z.enum(LogType)
279
+ }))
280
+ );
313
281
 
314
- useEffect(() => {
315
- const def = getDefaultHistory(terminal);
282
+ // const history = useState<LogItem[]>(storage);
283
+ const inputHistory = useState<string[]>([]);
316
284
 
317
- history[1](def);
318
- }, []);
285
+ const terminal = useMemo(() =>
286
+ new Terminal(id, name, history, inputHistory, onInput)
287
+ , [name, history, onInput]);
319
288
 
320
- useUpdateEffect(() => {
321
- localStorage.setItem(terminal.getLsKey(), JSON.stringify(history[0]));
322
- }, [history]);
289
+ // useUpdateEffect(() => {
290
+ // setStorage(history[0]);
291
+ // }, [history]);
323
292
 
324
293
  return terminal;
325
294
  }
@@ -1,3 +1,4 @@
1
+ import { forwardRef, RefObject, useDeferredValue, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
1
2
  import useDebugger from "../DebuggerContext";
2
3
  import { matchPath } from "../DebuggerLogic";
3
4
  import { Terminal } from "../DebuggerTerminal";
@@ -5,46 +6,152 @@ import DebugTerminal from "./DebugTerminal";
5
6
  import ValueArray from "./ValueArray";
6
7
  import ValueBoolean from "./ValueBoolean";
7
8
  import ValueFunction from "./ValueFunction";
8
- import ValueKeyword from "./ValueKeyword";
9
9
  import ValueNumber from "./ValueNumber";
10
10
  import ValueObject from "./ValueObject";
11
11
  import ValueString from "./ValueString";
12
12
  import ValueSymbol from "./ValueSymbol";
13
+ import { Nullable } from "@hrnec06/util";
14
+ import ContextMenu from "../../ContextMenu/ContextMenu";
15
+ import ValueConstant from "./ValueConstant";
16
+ import useSyncRef from "../../../hooks/useSyncRef";
17
+ import CTXM from "../../ContextMenu/ContextMenu.types";
18
+ import clsx from "clsx";
19
+ import useDebounce from "../../../hooks/useDebounce";
20
+ import { useCTXMListener } from "../../ContextMenu/ContextMenuCtx";
13
21
 
14
- interface ParseValueProps {
22
+ export interface ParseValueRef {
23
+ copyValue: string,
24
+ name?: string,
25
+ menu?: CTXM.ContextMenuSection,
26
+ }
27
+
28
+ interface ClassifyProps {
29
+ ref: React.Ref<ParseValueRef>,
15
30
  value: unknown,
16
31
  path?: string[]
17
32
  }
18
- export default function ParseValue({
33
+ function Classify({
34
+ ref,
19
35
  value,
20
36
  path = []
21
- }: ParseValueProps) {
37
+ }: ClassifyProps) {
22
38
  const debug = useDebugger();
23
39
 
24
40
  switch (typeof value) {
25
- case 'string': return (<ValueString value={value} />);
41
+ case 'string': return (<ValueString ref={ref} value={value} />);
26
42
  case 'bigint':
27
- case 'number': return (<ValueNumber value={value} />);
28
- case 'boolean': return (<ValueBoolean value={value} />);
29
- case 'undefined': return (<ValueKeyword value={"undefined"} />);
30
- case 'function': return (<ValueFunction value={value} />);
43
+ case 'number': return (<ValueNumber ref={ref} value={value} />);
44
+ case 'boolean': return (<ValueBoolean ref={ref} value={value} />);
45
+ case 'undefined': return (<ValueConstant ref={ref} value="undefined" />)
46
+ case 'function': return (<ValueFunction ref={ref} value={value} />);
31
47
  case 'symbol': return (<ValueSymbol value={value} />)
32
48
  case 'object': {
33
49
  if (value === null)
34
- return (<ValueKeyword value={"null"} />)
50
+ return (<ValueConstant ref={ref} value={"null"} />)
35
51
 
36
52
  const isRootObject = path.length === 0;
37
53
 
38
54
  const shouldBeExpanded = (isRootObject && debug.options.openRoot) || (matchPath(path, debug.paths.open) && !matchPath(path, debug.paths.exclude));
39
55
 
40
56
  if (value instanceof Terminal)
41
- return <DebugTerminal terminal={value} path={path} defaultExpanded={shouldBeExpanded} />
57
+ return <DebugTerminal ref={ref} terminal={value} path={path} defaultExpanded={shouldBeExpanded} />
42
58
 
43
59
  if (Array.isArray(value)) {
44
- return <ValueArray value={value} path={path} defaultExpanded={shouldBeExpanded} />
60
+ return <ValueArray ref={ref} value={value} path={path} defaultExpanded={shouldBeExpanded} />
45
61
  }
46
62
 
47
- return <ValueObject value={value} path={path} defaultExpanded={shouldBeExpanded} />
63
+ return <ValueObject ref={ref} value={value} path={path} defaultExpanded={shouldBeExpanded} />
48
64
  }
49
65
  }
50
- }
66
+ }
67
+
68
+ interface ParseValueProps {
69
+ value: unknown,
70
+ path?: string[],
71
+ /**
72
+ * Children to be put before the parsed value
73
+ */
74
+ children?: React.ReactNode
75
+ }
76
+ function ParseValue({
77
+ value,
78
+ path = [],
79
+ children
80
+ }: ParseValueProps)
81
+ {
82
+ const valueRef = useRef<ParseValueRef>(null);
83
+ const [localValue, setLocalValue] = useState<Nullable<ParseValueRef>>(null);
84
+ const syncedValueRef = useSyncRef(valueRef, setLocalValue);
85
+
86
+ return (
87
+ <DebugCTXMProvider
88
+ path={path}
89
+ ref={localValue}
90
+ >
91
+ {children}
92
+
93
+ <Classify
94
+ ref={syncedValueRef}
95
+ value={value}
96
+ path={path}
97
+ />
98
+ </DebugCTXMProvider>
99
+ );
100
+ }
101
+
102
+ interface DebugCTXMProviderProps extends React.PropsWithChildren {
103
+ path: string[],
104
+ ref: Nullable<ParseValueRef>
105
+ }
106
+ export function DebugCTXMProvider({
107
+ path,
108
+ ref,
109
+ children
110
+ }: DebugCTXMProviderProps)
111
+ {
112
+ const ctxmLabel = useMemo(() => {
113
+ if (!path.length)
114
+ return "root";
115
+
116
+ return path.join('.');
117
+ }, [path]);
118
+
119
+ const handleCopy = ref === null
120
+ ? undefined
121
+ : () => {
122
+ if (!ref) return;
123
+
124
+ navigator.clipboard.writeText(ref.copyValue);
125
+ };
126
+
127
+ return (
128
+ <ContextMenu
129
+ className={clsx(
130
+ "inline align-middle h-full",
131
+ "outline-sky-500 outline-offset-2",
132
+ )}
133
+
134
+ $autoGenerateWrapper
135
+ $id={ctxmLabel}
136
+ $inherit={(menu) => ({
137
+ parent: menu.id,
138
+ children: menu.menu
139
+ })}
140
+ $menu={[
141
+ [
142
+ {
143
+ id: 'copy',
144
+ label: "Copy",
145
+ description: ref?.name,
146
+ onClick: handleCopy
147
+ },
148
+ ],
149
+ ref?.menu ?? null
150
+ ]}
151
+ >
152
+ {children}
153
+ </ContextMenu>
154
+ )
155
+ }
156
+
157
+ export default ParseValue;
@@ -1,8 +1,8 @@
1
- import { createContext, useContext, useLayoutEffect, useRef, useState } from "react";
1
+ import { createContext, forwardRef, useContext, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
2
2
  import { LogItem, LogRole, LogType, Terminal } from "../DebuggerTerminal"
3
3
  import clsx from "clsx";
4
4
  import { KeyboardButton, matchBool, matchKeyEvent, matchMouseEvent, MouseButton, Optional } from "@hrnec06/util";
5
- import ParseValue from "./DebugParser";
5
+ import ParseValue, { ParseValueRef } from "./DebugParser";
6
6
  import { ChevronRight, CircleAlert, SendIcon, TerminalIcon, Trash, TriangleAlert } from "lucide-react";
7
7
  import ResizeableBox from "../../ResizeableBox/ResizeableBox";
8
8
  import { Chevron_Toggle } from "../DebuggerSymbols";
@@ -28,10 +28,25 @@ interface DebugTerminalProps {
28
28
  path: string[],
29
29
  defaultExpanded?: boolean
30
30
  }
31
- export default function DebugTerminal({ terminal, path, defaultExpanded }: DebugTerminalProps)
32
- {
31
+ const DebugTerminal = forwardRef<ParseValueRef, DebugTerminalProps>(({
32
+ terminal,
33
+ path,
34
+ defaultExpanded
35
+ }, ref) => {
33
36
  const [showing, setShowing] = useState(!!defaultExpanded);
34
37
 
38
+ useImperativeHandle(ref, () => ({
39
+ name: `Terminal ${terminal.name}`,
40
+ copyValue: terminal.getLog().reduce((prev, curr) => prev + `${curr.role === LogRole.Input ? '> ' : `${LogType[curr.type]}: `}${JSON.stringify(curr.value)}` + "\n", ""),
41
+ menu: [
42
+ {
43
+ id: "terminal-clear",
44
+ label: "Clear",
45
+ onClick: () => terminal.clear(),
46
+ }
47
+ ]
48
+ }), [terminal.getLog()]);
49
+
35
50
  const handleToggle = (value: boolean) => {
36
51
  setShowing(value);
37
52
  }
@@ -60,8 +75,8 @@ export default function DebugTerminal({ terminal, path, defaultExpanded }: Debug
60
75
 
61
76
  <TerminalMain show={showing} />
62
77
  </LocalTerminalContext.Provider>
63
- )
64
- }
78
+ );
79
+ });
65
80
 
66
81
  interface TerminalMainProps {
67
82
  show: boolean
@@ -338,4 +353,6 @@ function TerminalInput()
338
353
  </button>
339
354
  </div>
340
355
  )
341
- }
356
+ }
357
+
358
+ export default DebugTerminal;
@@ -1,23 +1,37 @@
1
- import React, { useLayoutEffect, useMemo, useState } from "react";
1
+ import React, { forwardRef, useImperativeHandle, useLayoutEffect, useMemo, useState } from "react";
2
2
  import useDebugger from "../DebuggerContext";
3
3
  import { Char_Bracket, Char_Comma, Chevron_Toggle } from "../DebuggerSymbols";
4
4
  import ParseValueSimple from "./DebugParserSimple";
5
- import ParseValue from "./DebugParser";
5
+ import ParseValue, { ParseValueRef } from "./DebugParser";
6
6
 
7
7
  interface ValueArrayProps {
8
8
  value: unknown[],
9
9
  path: string[],
10
10
  defaultExpanded?: boolean
11
11
  }
12
- export default function ValueArray({
12
+ const ValueArray = forwardRef<ParseValueRef, ValueArrayProps>(({
13
13
  value,
14
14
  path,
15
15
  defaultExpanded = false
16
- }: ValueArrayProps) {
16
+ }, ref) => {
17
17
  const debug = useDebugger();
18
18
 
19
19
  const [expanded, setExpanded] = useState(defaultExpanded);
20
20
 
21
+ useImperativeHandle(ref, () => ({
22
+ name: `Array (${value.length})`,
23
+ copyValue: JSON.stringify(value),
24
+ menu: [
25
+ {
26
+ id: "open-toggle",
27
+ label: expanded ? "Collapse" : "Expand",
28
+ onClick: () => {
29
+ setExpanded(!expanded);
30
+ }
31
+ }
32
+ ]
33
+ }), [value, expanded]);
34
+
21
35
  const children = useMemo(() => {
22
36
  const children: React.ReactNode[] = [];
23
37
 
@@ -106,7 +120,6 @@ export default function ValueArray({
106
120
  onClick={() => setExpanded(true)}
107
121
  >
108
122
  <Char_Bracket text="[" />
109
- {/* <span className="text-zinc-500">...</span> */}
110
123
  {collapsedPreview}
111
124
  <Char_Bracket text="]" />
112
125
  </div>
@@ -114,7 +127,7 @@ export default function ValueArray({
114
127
  }
115
128
  </>
116
129
  );
117
- }
130
+ });
118
131
 
119
132
  interface CompactArrayItemProps {
120
133
  value: unknown,
@@ -140,4 +153,6 @@ function CompactArrayItem({
140
153
  {isLast && (<Char_Comma />)}
141
154
  </>
142
155
  )
143
- }
156
+ }
157
+
158
+ export default ValueArray;
@@ -1,10 +1,21 @@
1
+ import { forwardRef, useImperativeHandle } from "react"
1
2
  import ValueKeyword from "./ValueKeyword"
3
+ import { ParseValueRef } from "./DebugParser";
2
4
 
3
5
  interface ValueBooleanProps {
4
6
  value: boolean
5
7
  }
6
- export default function ValueBoolean({
8
+ const ValueBoolean = forwardRef<ParseValueRef, ValueBooleanProps>(({
7
9
  value
8
- }: ValueBooleanProps) {
9
- return <ValueKeyword value={value ? 'true' : 'false'} />
10
- }
10
+ }, ref) => {
11
+ useImperativeHandle(ref, () => ({
12
+ name: "Boolean",
13
+ copyValue: value ? "true" : "false"
14
+ }), [value]);
15
+
16
+ return (
17
+ <ValueKeyword value={value ? 'true' : 'false'} />
18
+ )
19
+ });
20
+
21
+ export default ValueBoolean;
@@ -0,0 +1,21 @@
1
+ import { forwardRef, useImperativeHandle } from "react";
2
+ import ValueKeyword from "./ValueKeyword";
3
+ import { ParseValueRef } from "./DebugParser";
4
+
5
+ interface ValueConstantProps {
6
+ value: string
7
+ }
8
+ const ValueConstant = forwardRef<ParseValueRef, ValueConstantProps>(({
9
+ value
10
+ }, ref) => {
11
+ useImperativeHandle(ref, () => ({
12
+ name: value,
13
+ copyValue: value
14
+ }), [value]);
15
+
16
+ return (
17
+ <ValueKeyword value={value} />
18
+ )
19
+ });
20
+
21
+ export default ValueConstant;