@visual-json/react 0.1.0 → 0.2.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/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/json-editor.tsx
2
2
  import {
3
- useState as useState7,
4
- useRef as useRef8,
5
- useCallback as useCallback7,
6
- useEffect as useEffect7
3
+ useState as useState8,
4
+ useRef as useRef9,
5
+ useCallback as useCallback8,
6
+ useEffect as useEffect8
7
7
  } from "react";
8
8
 
9
9
  // src/visual-json.tsx
@@ -33,6 +33,91 @@ function useStudio() {
33
33
  return ctx;
34
34
  }
35
35
 
36
+ // src/get-visible-nodes.ts
37
+ function getVisibleNodes(root, isExpanded) {
38
+ const result = [];
39
+ function walk(node) {
40
+ result.push(node);
41
+ if (isExpanded(node.id) && (node.type === "object" || node.type === "array")) {
42
+ for (const child of node.children) {
43
+ walk(child);
44
+ }
45
+ }
46
+ }
47
+ walk(root);
48
+ return result;
49
+ }
50
+
51
+ // src/selection-utils.ts
52
+ import { removeNode } from "@visual-json/core";
53
+ function computeSelectAllIds(tree, focusedNodeId, currentlySelected) {
54
+ if (!focusedNodeId) return null;
55
+ let node = tree.nodesById.get(focusedNodeId);
56
+ if (!node) return null;
57
+ while (node) {
58
+ const parent = node.parentId ? tree.nodesById.get(node.parentId) : void 0;
59
+ const siblings = parent ? parent.children : [tree.root];
60
+ const siblingIds = new Set(siblings.map((s) => s.id));
61
+ const allSelected = siblings.every((s) => currentlySelected.has(s.id));
62
+ if (!allSelected) {
63
+ return siblingIds;
64
+ }
65
+ if (!parent || !parent.parentId) return siblingIds;
66
+ node = parent;
67
+ }
68
+ return null;
69
+ }
70
+ function computeRangeIds(visibleNodes, anchorId, targetId) {
71
+ const anchorIdx = visibleNodes.findIndex((n) => n.id === anchorId);
72
+ const targetIdx = visibleNodes.findIndex((n) => n.id === targetId);
73
+ if (anchorIdx === -1 || targetIdx === -1) return null;
74
+ const start = Math.min(anchorIdx, targetIdx);
75
+ const end = Math.max(anchorIdx, targetIdx);
76
+ const ids = /* @__PURE__ */ new Set();
77
+ for (let i = start; i <= end; i++) {
78
+ ids.add(visibleNodes[i].id);
79
+ }
80
+ return ids;
81
+ }
82
+ function deleteSelectedNodes(tree, selectedIds, visibleNodes) {
83
+ const idsToDelete = [...selectedIds].filter((id) => {
84
+ const node = tree.nodesById.get(id);
85
+ if (!node || node.parentId === null) return false;
86
+ let cur = tree.nodesById.get(node.parentId);
87
+ while (cur) {
88
+ if (selectedIds.has(cur.id)) return false;
89
+ cur = cur.parentId ? tree.nodesById.get(cur.parentId) : void 0;
90
+ }
91
+ return true;
92
+ });
93
+ if (idsToDelete.length === 0) return { newTree: tree, nextFocusId: null };
94
+ const firstDeletedIdx = visibleNodes.findIndex((n) => selectedIds.has(n.id));
95
+ let newTree = tree;
96
+ for (const id of idsToDelete) {
97
+ if (newTree.nodesById.has(id)) {
98
+ newTree = removeNode(newTree, id);
99
+ }
100
+ }
101
+ let nextFocusId = null;
102
+ for (let i = firstDeletedIdx; i < visibleNodes.length; i++) {
103
+ const id = visibleNodes[i].id;
104
+ if (!selectedIds.has(id) && newTree.nodesById.has(id)) {
105
+ nextFocusId = id;
106
+ break;
107
+ }
108
+ }
109
+ if (!nextFocusId) {
110
+ for (let i = firstDeletedIdx - 1; i >= 0; i--) {
111
+ const id = visibleNodes[i].id;
112
+ if (!selectedIds.has(id) && newTree.nodesById.has(id)) {
113
+ nextFocusId = id;
114
+ break;
115
+ }
116
+ }
117
+ }
118
+ return { newTree, nextFocusId };
119
+ }
120
+
36
121
  // src/visual-json.tsx
37
122
  import { jsx } from "react/jsx-runtime";
38
123
  function collectAllIds(node) {
@@ -49,10 +134,42 @@ function VisualJson({
49
134
  children
50
135
  }) {
51
136
  const [tree, setTreeState] = useState(() => fromJson(value));
52
- const [selectedNodeId, setSelectedNodeId] = useState(null);
137
+ const [focusedNodeId, setFocusedNodeId] = useState(null);
138
+ const [selectedNodeIds, setSelectedNodeIdsState] = useState(
139
+ () => /* @__PURE__ */ new Set()
140
+ );
141
+ const selectedNodeIdsRef = useRef(/* @__PURE__ */ new Set());
142
+ const setSelectedNodeIds = useCallback((ids) => {
143
+ selectedNodeIdsRef.current = ids;
144
+ setSelectedNodeIdsState(ids);
145
+ }, []);
146
+ const anchorNodeIdRef = useRef(null);
147
+ const [anchorNodeId, setAnchorNodeIdState] = useState(null);
148
+ const [drillDownNodeId, setDrillDownNodeId] = useState(null);
53
149
  const [expandedNodeIds, setExpandedNodeIds] = useState(
54
150
  () => /* @__PURE__ */ new Set([tree.root.id])
55
151
  );
152
+ const setAnchorNodeId = useCallback((id) => {
153
+ anchorNodeIdRef.current = id;
154
+ setAnchorNodeIdState(id);
155
+ }, []);
156
+ const focusSelectAndDrillDown = useCallback(
157
+ (nodeId) => {
158
+ setFocusedNodeId(nodeId);
159
+ setSelectedNodeIds(nodeId ? /* @__PURE__ */ new Set([nodeId]) : /* @__PURE__ */ new Set());
160
+ setAnchorNodeId(nodeId);
161
+ setDrillDownNodeId(nodeId);
162
+ },
163
+ [setSelectedNodeIds, setAnchorNodeId]
164
+ );
165
+ const visibleNodes = useMemo(
166
+ () => getVisibleNodes(tree.root, (id) => expandedNodeIds.has(id)),
167
+ [tree.root, expandedNodeIds]
168
+ );
169
+ const visibleNodesOverrideRef = useRef(null);
170
+ const setVisibleNodesOverride = useCallback((nodes) => {
171
+ visibleNodesOverrideRef.current = nodes;
172
+ }, []);
56
173
  const historyRef = useRef(new History());
57
174
  const isInternalChange = useRef(false);
58
175
  const hasMounted = useRef(false);
@@ -81,7 +198,7 @@ function VisualJson({
81
198
  const newTree = fromJson(value);
82
199
  setTreeState(newTree);
83
200
  setExpandedNodeIds(/* @__PURE__ */ new Set([newTree.root.id]));
84
- setSelectedNodeId(null);
201
+ focusSelectAndDrillDown(null);
85
202
  historyRef.current = new History();
86
203
  historyRef.current.push(newTree);
87
204
  setCanUndo(false);
@@ -139,8 +256,66 @@ function VisualJson({
139
256
  document.addEventListener("keydown", handleKeyDown);
140
257
  return () => document.removeEventListener("keydown", handleKeyDown);
141
258
  }, [undo, redo]);
142
- const selectNode = useCallback((nodeId) => {
143
- setSelectedNodeId(nodeId);
259
+ const selectNode = useCallback(
260
+ (nodeId) => {
261
+ setFocusedNodeId(nodeId);
262
+ setSelectedNodeIds(nodeId ? /* @__PURE__ */ new Set([nodeId]) : /* @__PURE__ */ new Set());
263
+ setAnchorNodeId(nodeId);
264
+ },
265
+ [setSelectedNodeIds, setAnchorNodeId]
266
+ );
267
+ const selectAndDrillDown = focusSelectAndDrillDown;
268
+ const toggleNodeSelection = useCallback(
269
+ (nodeId) => {
270
+ const next = new Set(selectedNodeIdsRef.current);
271
+ if (next.has(nodeId)) {
272
+ next.delete(nodeId);
273
+ } else {
274
+ next.add(nodeId);
275
+ }
276
+ setSelectedNodeIds(next);
277
+ if (next.size === 0) {
278
+ setFocusedNodeId(null);
279
+ setAnchorNodeId(null);
280
+ } else {
281
+ setFocusedNodeId(nodeId);
282
+ setAnchorNodeId(nodeId);
283
+ }
284
+ },
285
+ [setSelectedNodeIds, setAnchorNodeId]
286
+ );
287
+ const selectNodeRange = useCallback(
288
+ (toNodeId) => {
289
+ const nodes = visibleNodesOverrideRef.current ?? visibleNodes;
290
+ const anchor = anchorNodeIdRef.current;
291
+ if (!anchor) {
292
+ setFocusedNodeId(toNodeId);
293
+ setSelectedNodeIds(/* @__PURE__ */ new Set([toNodeId]));
294
+ setAnchorNodeId(toNodeId);
295
+ return;
296
+ }
297
+ const rangeIds = computeRangeIds(nodes, anchor, toNodeId);
298
+ if (!rangeIds) {
299
+ setFocusedNodeId(toNodeId);
300
+ setSelectedNodeIds(/* @__PURE__ */ new Set([toNodeId]));
301
+ setAnchorNodeId(toNodeId);
302
+ return;
303
+ }
304
+ setSelectedNodeIds(rangeIds);
305
+ setFocusedNodeId(toNodeId);
306
+ },
307
+ [visibleNodes, setSelectedNodeIds, setAnchorNodeId]
308
+ );
309
+ const setSelection = useCallback(
310
+ (focusedId, newSelectedIds, newAnchorId) => {
311
+ setFocusedNodeId(focusedId);
312
+ setSelectedNodeIds(newSelectedIds);
313
+ setAnchorNodeId(newAnchorId);
314
+ },
315
+ [setSelectedNodeIds, setAnchorNodeId]
316
+ );
317
+ const drillDown = useCallback((nodeId) => {
318
+ setDrillDownNodeId(nodeId);
144
319
  }, []);
145
320
  const toggleExpand = useCallback((nodeId) => {
146
321
  setExpandedNodeIds((prev) => {
@@ -186,6 +361,7 @@ function VisualJson({
186
361
  const matchIds = new Set(matches.map((m) => m.nodeId));
187
362
  setSearchMatchNodeIds(matchIds);
188
363
  if (matches.length > 0) {
364
+ const firstId = matches[0].nodeId;
189
365
  const ancestors = getAncestorIds(
190
366
  tree,
191
367
  matches.map((m) => m.nodeId)
@@ -195,7 +371,7 @@ function VisualJson({
195
371
  for (const id of ancestors) next.add(id);
196
372
  return next;
197
373
  });
198
- setSelectedNodeId(matches[0].nodeId);
374
+ focusSelectAndDrillDown(firstId);
199
375
  }
200
376
  },
201
377
  [tree]
@@ -204,14 +380,14 @@ function VisualJson({
204
380
  if (searchMatches.length === 0) return;
205
381
  const nextIdx = (searchMatchIndex + 1) % searchMatches.length;
206
382
  setSearchMatchIndex(nextIdx);
207
- setSelectedNodeId(searchMatches[nextIdx].nodeId);
208
- }, [searchMatches, searchMatchIndex]);
383
+ focusSelectAndDrillDown(searchMatches[nextIdx].nodeId);
384
+ }, [searchMatches, searchMatchIndex, focusSelectAndDrillDown]);
209
385
  const prevSearchMatch = useCallback(() => {
210
386
  if (searchMatches.length === 0) return;
211
387
  const prevIdx = (searchMatchIndex - 1 + searchMatches.length) % searchMatches.length;
212
388
  setSearchMatchIndex(prevIdx);
213
- setSelectedNodeId(searchMatches[prevIdx].nodeId);
214
- }, [searchMatches, searchMatchIndex]);
389
+ focusSelectAndDrillDown(searchMatches[prevIdx].nodeId);
390
+ }, [searchMatches, searchMatchIndex, focusSelectAndDrillDown]);
215
391
  useEffect(() => {
216
392
  if (!searchQuery.trim()) return;
217
393
  const matches = searchNodes(tree, searchQuery);
@@ -224,7 +400,10 @@ function VisualJson({
224
400
  const state = useMemo(
225
401
  () => ({
226
402
  tree,
227
- selectedNodeId,
403
+ focusedNodeId,
404
+ selectedNodeIds,
405
+ anchorNodeId,
406
+ drillDownNodeId,
228
407
  expandedNodeIds,
229
408
  schema: schema ?? null,
230
409
  searchQuery,
@@ -234,7 +413,10 @@ function VisualJson({
234
413
  }),
235
414
  [
236
415
  tree,
237
- selectedNodeId,
416
+ focusedNodeId,
417
+ selectedNodeIds,
418
+ anchorNodeId,
419
+ drillDownNodeId,
238
420
  expandedNodeIds,
239
421
  schema,
240
422
  searchQuery,
@@ -247,6 +429,12 @@ function VisualJson({
247
429
  () => ({
248
430
  setTree,
249
431
  selectNode,
432
+ selectAndDrillDown,
433
+ toggleNodeSelection,
434
+ selectNodeRange,
435
+ setSelection,
436
+ setVisibleNodesOverride,
437
+ drillDown,
250
438
  toggleExpand,
251
439
  expandNode,
252
440
  collapseNode,
@@ -263,6 +451,12 @@ function VisualJson({
263
451
  [
264
452
  setTree,
265
453
  selectNode,
454
+ selectAndDrillDown,
455
+ toggleNodeSelection,
456
+ selectNodeRange,
457
+ setSelection,
458
+ setVisibleNodesOverride,
459
+ drillDown,
266
460
  toggleExpand,
267
461
  expandNode,
268
462
  collapseNode,
@@ -282,11 +476,9 @@ function VisualJson({
282
476
  }
283
477
 
284
478
  // src/tree-view.tsx
285
- import { useState as useState4, useMemo as useMemo2, useRef as useRef4, useCallback as useCallback3, useEffect as useEffect3 } from "react";
479
+ import { useState as useState4, useMemo as useMemo3, useRef as useRef4, useCallback as useCallback3, useEffect as useEffect3 } from "react";
286
480
  import {
287
- removeNode,
288
- getPropertySchema,
289
- validateNode,
481
+ removeNode as removeNode3,
290
482
  duplicateNode,
291
483
  changeType,
292
484
  toJson as toJson2
@@ -404,96 +596,199 @@ function getDisplayKey(node, state) {
404
596
  return node.key;
405
597
  }
406
598
 
407
- // src/get-visible-nodes.ts
408
- function getVisibleNodes(root, isExpanded) {
409
- const result = [];
410
- function walk(node) {
411
- result.push(node);
412
- if (isExpanded(node.id) && (node.type === "object" || node.type === "array")) {
413
- for (const child of node.children) {
414
- walk(child);
415
- }
416
- }
417
- }
418
- walk(root);
419
- return result;
420
- }
599
+ // src/use-drag-drop.ts
600
+ import { useState as useState3, useCallback as useCallback2, useRef as useRef3, useMemo as useMemo2 } from "react";
601
+ import {
602
+ removeNode as removeNode2,
603
+ insertNode,
604
+ reorderChildrenMulti,
605
+ isDescendant
606
+ } from "@visual-json/core";
607
+
608
+ // src/theme.ts
609
+ var DEFAULT_CSS_VARS = {
610
+ "--vj-bg": "#1e1e1e",
611
+ "--vj-bg-panel": "#252526",
612
+ "--vj-bg-hover": "#2a2d2e",
613
+ "--vj-bg-selected": "#2a5a1e",
614
+ "--vj-bg-selected-muted": "#2a2d2e",
615
+ "--vj-bg-match": "#3a3520",
616
+ "--vj-bg-match-active": "#51502b",
617
+ "--vj-border": "#333333",
618
+ "--vj-border-subtle": "#2a2a2a",
619
+ "--vj-text": "#cccccc",
620
+ "--vj-text-muted": "#888888",
621
+ "--vj-text-dim": "#666666",
622
+ "--vj-text-dimmer": "#555555",
623
+ "--vj-string": "#ce9178",
624
+ "--vj-number": "#b5cea8",
625
+ "--vj-boolean": "#569cd6",
626
+ "--vj-accent": "#007acc",
627
+ "--vj-accent-muted": "#094771",
628
+ "--vj-input-bg": "#3c3c3c",
629
+ "--vj-input-border": "#555555",
630
+ "--vj-error": "#f48771",
631
+ "--vj-font": "monospace",
632
+ "--vj-input-font-size": "13px"
633
+ };
421
634
 
422
635
  // src/use-drag-drop.ts
423
- import { useState as useState3, useCallback as useCallback2, useRef as useRef3 } from "react";
424
- import { reorderChildren, moveNode } from "@visual-json/core";
636
+ var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
425
637
  var INITIAL_DRAG_STATE = {
426
- draggedNodeId: null,
638
+ draggedNodeIds: EMPTY_SET,
427
639
  dropTargetNodeId: null,
428
640
  dropPosition: null
429
641
  };
430
- function useDragDrop() {
642
+ function sortByTreeOrder(root, ids) {
643
+ const result = [];
644
+ function walk(node) {
645
+ if (ids.has(node.id)) result.push(node.id);
646
+ for (const child of node.children) walk(child);
647
+ }
648
+ walk(root);
649
+ return result;
650
+ }
651
+ function setMultiDragImage(e, count) {
652
+ const ghost = document.createElement("div");
653
+ ghost.textContent = `${count} selected`;
654
+ const root = document.querySelector("[data-form-container], [role='tree']");
655
+ const cs = root ? getComputedStyle(root) : null;
656
+ const bg = cs?.getPropertyValue("--vj-bg-selected").trim() || DEFAULT_CSS_VARS["--vj-bg-selected"];
657
+ const fg = cs?.getPropertyValue("--vj-text-selected").trim() || cs?.getPropertyValue("--vj-text").trim() || DEFAULT_CSS_VARS["--vj-text"];
658
+ const font = cs?.getPropertyValue("--vj-font").trim() || DEFAULT_CSS_VARS["--vj-font"];
659
+ ghost.style.cssText = [
660
+ "position:fixed",
661
+ "top:-1000px",
662
+ "left:-1000px",
663
+ "padding:4px 12px",
664
+ `background:${bg}`,
665
+ `color:${fg}`,
666
+ `font-family:${font}`,
667
+ "font-size:13px",
668
+ "border-radius:4px",
669
+ "white-space:nowrap",
670
+ "pointer-events:none"
671
+ ].join(";");
672
+ document.body.appendChild(ghost);
673
+ e.dataTransfer.setDragImage(ghost, 0, 14);
674
+ requestAnimationFrame(() => ghost.remove());
675
+ }
676
+ function useDragDrop(visibleNodes, selectedNodeIds) {
431
677
  const { state, actions } = useStudio();
432
678
  const [dragState, setDragState] = useState3(INITIAL_DRAG_STATE);
433
679
  const dragStateRef = useRef3(dragState);
434
680
  dragStateRef.current = dragState;
435
- const handleDragStart = useCallback2((nodeId) => {
436
- setDragState({
437
- draggedNodeId: nodeId,
438
- dropTargetNodeId: null,
439
- dropPosition: null
440
- });
441
- }, []);
442
- const handleDragOver = useCallback2(
681
+ const visibleNodeIndexMap = useMemo2(() => {
682
+ const map = /* @__PURE__ */ new Map();
683
+ visibleNodes.forEach((n, i) => map.set(n.id, i));
684
+ return map;
685
+ }, [visibleNodes]);
686
+ const handleDragStart = useCallback2(
687
+ (nodeId) => {
688
+ let ids;
689
+ if (selectedNodeIds.size > 0 && selectedNodeIds.has(nodeId)) {
690
+ ids = selectedNodeIds;
691
+ } else {
692
+ ids = /* @__PURE__ */ new Set([nodeId]);
693
+ }
694
+ setDragState({
695
+ draggedNodeIds: ids,
696
+ dropTargetNodeId: null,
697
+ dropPosition: null
698
+ });
699
+ },
700
+ [selectedNodeIds]
701
+ );
702
+ const rawDragOver = useCallback2(
443
703
  (nodeId, position) => {
704
+ const draggedIds = dragStateRef.current.draggedNodeIds;
705
+ for (const draggedId of draggedIds) {
706
+ if (nodeId === draggedId || isDescendant(state.tree, nodeId, draggedId)) {
707
+ return;
708
+ }
709
+ }
444
710
  setDragState((prev) => ({
445
711
  ...prev,
446
712
  dropTargetNodeId: nodeId,
447
713
  dropPosition: position
448
714
  }));
449
715
  },
450
- []
716
+ [state.tree]
717
+ );
718
+ const handleDragOver = useCallback2(
719
+ (nodeId, position) => {
720
+ if (position === "before") {
721
+ const idx = visibleNodeIndexMap.get(nodeId);
722
+ if (idx !== void 0 && idx > 0) {
723
+ rawDragOver(visibleNodes[idx - 1].id, "after");
724
+ return;
725
+ }
726
+ }
727
+ rawDragOver(nodeId, position);
728
+ },
729
+ [visibleNodes, visibleNodeIndexMap, rawDragOver]
451
730
  );
452
731
  const handleDragEnd = useCallback2(() => {
453
732
  setDragState(INITIAL_DRAG_STATE);
454
733
  }, []);
455
734
  const handleDrop = useCallback2(() => {
456
- const { draggedNodeId, dropTargetNodeId, dropPosition } = dragStateRef.current;
457
- if (!draggedNodeId || !dropTargetNodeId || !dropPosition) return;
458
- const draggedNode = state.tree.nodesById.get(draggedNodeId);
735
+ const { draggedNodeIds, dropTargetNodeId, dropPosition } = dragStateRef.current;
736
+ if (draggedNodeIds.size === 0 || !dropTargetNodeId || !dropPosition) return;
459
737
  const targetNode = state.tree.nodesById.get(dropTargetNodeId);
460
- if (!draggedNode || !targetNode) return;
461
- if (draggedNode.parentId && draggedNode.parentId === targetNode.parentId) {
462
- const parent = state.tree.nodesById.get(draggedNode.parentId);
463
- if (parent) {
464
- const fromIndex = parent.children.findIndex(
465
- (c) => c.id === draggedNodeId
466
- );
467
- let toIndex = parent.children.findIndex(
468
- (c) => c.id === dropTargetNodeId
469
- );
470
- if (dropPosition === "after") toIndex++;
471
- if (fromIndex < toIndex) toIndex--;
472
- if (fromIndex !== toIndex && fromIndex >= 0 && toIndex >= 0) {
473
- const newTree = reorderChildren(
474
- state.tree,
475
- parent.id,
476
- fromIndex,
477
- toIndex
478
- );
479
- actions.setTree(newTree);
738
+ if (!targetNode || !targetNode.parentId) return;
739
+ for (const id of draggedNodeIds) {
740
+ if (isDescendant(state.tree, dropTargetNodeId, id)) return;
741
+ }
742
+ const targetParentId = targetNode.parentId;
743
+ const targetParent = state.tree.nodesById.get(targetParentId);
744
+ if (!targetParent) return;
745
+ const parentChildren = targetParent.children;
746
+ const orderedDragIds = parentChildren.filter((c) => draggedNodeIds.has(c.id)).map((c) => c.id);
747
+ const allSameParent = orderedDragIds.length === draggedNodeIds.size && [...draggedNodeIds].every((id) => {
748
+ const n = state.tree.nodesById.get(id);
749
+ return n?.parentId === targetParentId;
750
+ });
751
+ if (allSameParent) {
752
+ const newTree = reorderChildrenMulti(
753
+ state.tree,
754
+ targetParentId,
755
+ orderedDragIds,
756
+ dropTargetNodeId,
757
+ dropPosition
758
+ );
759
+ actions.setTree(newTree);
760
+ } else {
761
+ const orderedIds = sortByTreeOrder(state.tree.root, draggedNodeIds);
762
+ const draggedNodes = orderedIds.map((id) => state.tree.nodesById.get(id)).filter((n) => !!n && n.parentId !== null).map((n) => structuredClone(n));
763
+ let newTree = state.tree;
764
+ for (const id of [...orderedIds].reverse()) {
765
+ if (newTree.nodesById.has(id)) {
766
+ newTree = removeNode2(newTree, id);
480
767
  }
481
768
  }
482
- } else if (targetNode.parentId) {
483
- const newParent = state.tree.nodesById.get(targetNode.parentId);
484
- if (newParent) {
485
- let toIndex = newParent.children.findIndex(
486
- (c) => c.id === dropTargetNodeId
487
- );
488
- if (dropPosition === "after") toIndex++;
489
- const newTree = moveNode(
490
- state.tree,
491
- draggedNodeId,
492
- newParent.id,
493
- toIndex
769
+ const updatedTarget = newTree.nodesById.get(dropTargetNodeId);
770
+ if (!updatedTarget || !updatedTarget.parentId) {
771
+ setDragState(INITIAL_DRAG_STATE);
772
+ return;
773
+ }
774
+ const updatedParent = newTree.nodesById.get(updatedTarget.parentId);
775
+ if (!updatedParent) {
776
+ setDragState(INITIAL_DRAG_STATE);
777
+ return;
778
+ }
779
+ let insertIdx = updatedParent.children.findIndex(
780
+ (c) => c.id === dropTargetNodeId
781
+ );
782
+ if (dropPosition === "after") insertIdx++;
783
+ for (let i = 0; i < draggedNodes.length; i++) {
784
+ newTree = insertNode(
785
+ newTree,
786
+ updatedParent.id,
787
+ draggedNodes[i],
788
+ insertIdx + i
494
789
  );
495
- actions.setTree(newTree);
496
790
  }
791
+ actions.setTree(newTree);
497
792
  }
498
793
  setDragState(INITIAL_DRAG_STATE);
499
794
  }, [state.tree, actions]);
@@ -515,6 +810,7 @@ function TreeNodeRow({
515
810
  showValues,
516
811
  showCounts,
517
812
  isFocused,
813
+ onSelectRange,
518
814
  onDragStart,
519
815
  onDragOver,
520
816
  onDragEnd,
@@ -522,19 +818,15 @@ function TreeNodeRow({
522
818
  onContextMenu
523
819
  }) {
524
820
  const { state, actions } = useStudio();
525
- const isSelected = state.selectedNodeId === node.id;
821
+ const isSelected = state.selectedNodeIds.has(node.id);
526
822
  const isExpanded = state.expandedNodeIds.has(node.id);
527
823
  const isContainer = node.type === "object" || node.type === "array";
528
824
  const [hovered, setHovered] = useState4(false);
529
825
  const isRoot = node.parentId === null;
530
826
  const isSearchMatch = state.searchMatchNodeIds.has(node.id);
531
827
  const isActiveMatch = state.searchMatches.length > 0 && state.searchMatches[state.searchMatchIndex]?.nodeId === node.id;
532
- const schema = state.schema;
533
- const nodeSchema = schema ? getPropertySchema(schema, node.path) : void 0;
534
- const validation = nodeSchema ? validateNode(node, nodeSchema) : null;
535
- const hasError = validation ? !validation.valid : false;
536
828
  const isDragTarget = dragState.dropTargetNodeId === node.id;
537
- const isDraggedNode = dragState.draggedNodeId === node.id;
829
+ const isDraggedNode = dragState.draggedNodeIds.has(node.id);
538
830
  function displayValue() {
539
831
  if (isContainer) {
540
832
  return node.type === "array" ? `[${node.children.length}]` : `{${node.children.length}}`;
@@ -550,12 +842,12 @@ function TreeNodeRow({
550
842
  const position = e.clientY < midY ? "before" : "after";
551
843
  onDragOver(node.id, position);
552
844
  }
553
- let borderTop = "none";
554
- let borderBottom = "none";
845
+ let borderTopColor = "transparent";
846
+ let borderBottomColor = "transparent";
555
847
  if (isDragTarget && dragState.dropPosition === "before") {
556
- borderTop = "2px solid var(--vj-accent, #007acc)";
848
+ borderTopColor = "var(--vj-accent, #007acc)";
557
849
  } else if (isDragTarget && dragState.dropPosition === "after") {
558
- borderBottom = "2px solid var(--vj-accent, #007acc)";
850
+ borderBottomColor = "var(--vj-accent, #007acc)";
559
851
  }
560
852
  return /* @__PURE__ */ jsxs(Fragment, { children: [
561
853
  /* @__PURE__ */ jsxs(
@@ -564,13 +856,24 @@ function TreeNodeRow({
564
856
  role: "treeitem",
565
857
  "aria-selected": isSelected,
566
858
  "aria-expanded": isContainer ? isExpanded : void 0,
567
- onClick: () => actions.selectNode(node.id),
859
+ onClick: (e) => {
860
+ if (e.shiftKey) {
861
+ onSelectRange(node.id);
862
+ } else if (e.metaKey || e.ctrlKey) {
863
+ actions.toggleNodeSelection(node.id);
864
+ } else {
865
+ actions.selectAndDrillDown(node.id);
866
+ }
867
+ },
568
868
  onMouseEnter: () => setHovered(true),
569
869
  onMouseLeave: () => setHovered(false),
570
870
  onContextMenu: (e) => onContextMenu(e, node),
571
871
  draggable: !isRoot,
572
872
  onDragStart: (e) => {
573
873
  e.dataTransfer.effectAllowed = "move";
874
+ if (state.selectedNodeIds.size > 1 && state.selectedNodeIds.has(node.id)) {
875
+ setMultiDragImage(e, state.selectedNodeIds.size);
876
+ }
574
877
  onDragStart(node.id);
575
878
  },
576
879
  onDragOver: handleDragOverEvent,
@@ -584,15 +887,16 @@ function TreeNodeRow({
584
887
  display: "flex",
585
888
  alignItems: "center",
586
889
  gap: 6,
587
- padding: "3px 8px",
890
+ padding: "1px 8px",
588
891
  paddingLeft: 8 + depth * 16,
589
892
  cursor: "pointer",
590
893
  backgroundColor: isSelected ? isFocused ? "var(--vj-bg-selected, #2a5a1e)" : "var(--vj-bg-selected-muted, var(--vj-bg-hover, #2a2d2e))" : isActiveMatch ? "var(--vj-bg-match-active, #51502b)" : isSearchMatch ? "var(--vj-bg-match, #3a3520)" : hovered ? "var(--vj-bg-hover, #2a2d2e)" : "transparent",
591
894
  minHeight: 28,
592
895
  userSelect: "none",
593
896
  opacity: isDraggedNode ? 0.4 : 1,
594
- borderTop,
595
- borderBottom,
897
+ borderTop: `2px solid ${borderTopColor}`,
898
+ borderBottom: `2px solid ${borderBottomColor}`,
899
+ boxSizing: "border-box",
596
900
  color: isSelected && isFocused ? "var(--vj-text-selected, var(--vj-text, #cccccc))" : "var(--vj-text, #cccccc)"
597
901
  },
598
902
  children: [
@@ -675,6 +979,7 @@ function TreeNodeRow({
675
979
  showValues,
676
980
  showCounts,
677
981
  isFocused,
982
+ onSelectRange,
678
983
  onDragStart,
679
984
  onDragOver,
680
985
  onDragEnd,
@@ -692,25 +997,34 @@ function TreeView({
692
997
  }) {
693
998
  const { state, actions } = useStudio();
694
999
  const containerRef = useRef4(null);
1000
+ const visibleNodes = useMemo3(
1001
+ () => getVisibleNodes(state.tree.root, (id) => state.expandedNodeIds.has(id)),
1002
+ [state.tree.root, state.expandedNodeIds]
1003
+ );
695
1004
  const {
696
1005
  dragState,
697
1006
  handleDragStart,
698
1007
  handleDragOver,
699
1008
  handleDragEnd,
700
1009
  handleDrop
701
- } = useDragDrop();
702
- const [contextMenu, setContextMenu] = useState4(null);
703
- const visibleNodes = useMemo2(
704
- () => getVisibleNodes(state.tree.root, (id) => state.expandedNodeIds.has(id)),
705
- [state.tree.root, state.expandedNodeIds]
1010
+ } = useDragDrop(visibleNodes, state.selectedNodeIds);
1011
+ const handleSelectRange = useCallback3(
1012
+ (nodeId) => {
1013
+ actions.setVisibleNodesOverride(visibleNodes);
1014
+ actions.selectNodeRange(nodeId);
1015
+ },
1016
+ [visibleNodes, actions]
706
1017
  );
1018
+ const [contextMenu, setContextMenu] = useState4(null);
707
1019
  const handleContextMenu = useCallback3(
708
1020
  (e, node) => {
709
1021
  e.preventDefault();
710
- actions.selectNode(node.id);
1022
+ if (!state.selectedNodeIds.has(node.id)) {
1023
+ actions.selectAndDrillDown(node.id);
1024
+ }
711
1025
  setContextMenu({ x: e.clientX, y: e.clientY, node });
712
1026
  },
713
- [actions]
1027
+ [actions, state.selectedNodeIds]
714
1028
  );
715
1029
  const buildContextMenuItems = useCallback3(
716
1030
  (node) => {
@@ -789,7 +1103,7 @@ function TreeView({
789
1103
  items.push({
790
1104
  label: "Delete",
791
1105
  action: () => {
792
- const newTree = removeNode(state.tree, node.id);
1106
+ const newTree = removeNode3(state.tree, node.id);
793
1107
  actions.setTree(newTree);
794
1108
  }
795
1109
  });
@@ -801,19 +1115,31 @@ function TreeView({
801
1115
  const handleKeyDown = useCallback3(
802
1116
  (e) => {
803
1117
  const currentIndex = visibleNodes.findIndex(
804
- (n) => n.id === state.selectedNodeId
1118
+ (n) => n.id === state.focusedNodeId
805
1119
  );
806
1120
  switch (e.key) {
807
1121
  case "ArrowDown": {
808
1122
  e.preventDefault();
809
1123
  const next = visibleNodes[currentIndex + 1];
810
- if (next) actions.selectNode(next.id);
1124
+ if (next) {
1125
+ if (e.shiftKey) {
1126
+ handleSelectRange(next.id);
1127
+ } else {
1128
+ actions.selectNode(next.id);
1129
+ }
1130
+ }
811
1131
  break;
812
1132
  }
813
1133
  case "ArrowUp": {
814
1134
  e.preventDefault();
815
1135
  const prev = visibleNodes[currentIndex - 1];
816
- if (prev) actions.selectNode(prev.id);
1136
+ if (prev) {
1137
+ if (e.shiftKey) {
1138
+ handleSelectRange(prev.id);
1139
+ } else {
1140
+ actions.selectNode(prev.id);
1141
+ }
1142
+ }
817
1143
  break;
818
1144
  }
819
1145
  case "ArrowRight": {
@@ -840,17 +1166,47 @@ function TreeView({
840
1166
  }
841
1167
  break;
842
1168
  }
1169
+ case "a": {
1170
+ if (e.metaKey || e.ctrlKey) {
1171
+ e.preventDefault();
1172
+ const ids = computeSelectAllIds(
1173
+ state.tree,
1174
+ state.focusedNodeId,
1175
+ state.selectedNodeIds
1176
+ );
1177
+ if (ids) {
1178
+ actions.setSelection(
1179
+ state.focusedNodeId,
1180
+ ids,
1181
+ state.focusedNodeId
1182
+ );
1183
+ }
1184
+ }
1185
+ break;
1186
+ }
1187
+ case "Escape": {
1188
+ e.preventDefault();
1189
+ if (state.selectedNodeIds.size > 1 && state.focusedNodeId) {
1190
+ actions.selectNode(state.focusedNodeId);
1191
+ } else {
1192
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
1193
+ }
1194
+ break;
1195
+ }
843
1196
  case "Delete":
844
1197
  case "Backspace": {
845
1198
  e.preventDefault();
846
- const toDelete = currentIndex >= 0 ? visibleNodes[currentIndex] : null;
847
- if (toDelete && toDelete.parentId) {
848
- const nextSelect = visibleNodes[currentIndex + 1] ?? visibleNodes[currentIndex - 1];
849
- const newTree = removeNode(state.tree, toDelete.id);
850
- actions.setTree(newTree);
851
- if (nextSelect && nextSelect.id !== toDelete.id) {
852
- actions.selectNode(nextSelect.id);
853
- }
1199
+ const { newTree, nextFocusId } = deleteSelectedNodes(
1200
+ state.tree,
1201
+ state.selectedNodeIds,
1202
+ visibleNodes
1203
+ );
1204
+ if (newTree === state.tree) break;
1205
+ actions.setTree(newTree);
1206
+ if (nextFocusId) {
1207
+ actions.selectNode(nextFocusId);
1208
+ } else {
1209
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
854
1210
  }
855
1211
  break;
856
1212
  }
@@ -858,23 +1214,25 @@ function TreeView({
858
1214
  },
859
1215
  [
860
1216
  visibleNodes,
861
- state.selectedNodeId,
1217
+ state.focusedNodeId,
1218
+ state.selectedNodeIds,
862
1219
  state.expandedNodeIds,
863
1220
  state.tree,
864
- actions
1221
+ actions,
1222
+ handleSelectRange
865
1223
  ]
866
1224
  );
867
1225
  const [isFocused, setIsFocused] = useState4(false);
868
1226
  useEffect3(() => {
869
- if (state.selectedNodeId && containerRef.current) {
1227
+ if (state.focusedNodeId && containerRef.current) {
870
1228
  const el = containerRef.current.querySelector(
871
- `[data-node-id="${state.selectedNodeId}"]`
1229
+ `[data-node-id="${state.focusedNodeId}"]`
872
1230
  );
873
1231
  if (el) {
874
1232
  el.scrollIntoView({ block: "nearest" });
875
1233
  }
876
1234
  }
877
- }, [state.selectedNodeId]);
1235
+ }, [state.focusedNodeId]);
878
1236
  return /* @__PURE__ */ jsxs(Fragment, { children: [
879
1237
  /* @__PURE__ */ jsx3(
880
1238
  "div",
@@ -904,6 +1262,7 @@ function TreeView({
904
1262
  showValues,
905
1263
  showCounts,
906
1264
  isFocused,
1265
+ onSelectRange: handleSelectRange,
907
1266
  onDragStart: handleDragStart,
908
1267
  onDragOver: handleDragOver,
909
1268
  onDragEnd: handleDragEnd,
@@ -926,25 +1285,25 @@ function TreeView({
926
1285
  }
927
1286
 
928
1287
  // src/form-view.tsx
929
- import { useState as useState6, useCallback as useCallback5, useRef as useRef6, useEffect as useEffect5, useMemo as useMemo4 } from "react";
1288
+ import { useState as useState7, useCallback as useCallback6, useRef as useRef7, useEffect as useEffect6, useMemo as useMemo6 } from "react";
930
1289
  import {
931
1290
  setValue,
932
1291
  setKey,
933
1292
  addProperty,
934
- removeNode as removeNode2,
935
- getPropertySchema as getPropertySchema2,
1293
+ removeNode as removeNode4,
1294
+ getPropertySchema,
936
1295
  resolveRef
937
1296
  } from "@visual-json/core";
938
1297
 
939
1298
  // src/breadcrumbs.tsx
940
- import { useState as useState5, useRef as useRef5, useEffect as useEffect4, useCallback as useCallback4, useMemo as useMemo3 } from "react";
1299
+ import { useState as useState5, useRef as useRef5, useEffect as useEffect4, useCallback as useCallback4, useMemo as useMemo4 } from "react";
941
1300
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
942
1301
  var MAX_SUGGESTIONS = 20;
943
1302
  var DROPDOWN_MAX_HEIGHT = 200;
944
1303
  function Breadcrumbs({ className }) {
945
1304
  const { state, actions } = useStudio();
946
- const selectedNode = state.selectedNodeId ? state.tree.nodesById.get(state.selectedNodeId) : null;
947
- const currentPath = selectedNode?.path ?? "/";
1305
+ const drillDownNode = state.drillDownNodeId ? state.tree.nodesById.get(state.drillDownNodeId) : null;
1306
+ const currentPath = drillDownNode?.path ?? "/";
948
1307
  const [inputValue, setInputValue] = useState5(currentPath);
949
1308
  const [open, setOpen] = useState5(false);
950
1309
  const [highlightIndex, setHighlightIndex] = useState5(0);
@@ -954,7 +1313,7 @@ function Breadcrumbs({ className }) {
954
1313
  useEffect4(() => {
955
1314
  setInputValue(currentPath);
956
1315
  }, [currentPath]);
957
- const suggestions = useMemo3(() => {
1316
+ const suggestions = useMemo4(() => {
958
1317
  if (!open) return [];
959
1318
  const query = inputValue.toLowerCase();
960
1319
  const matches = [];
@@ -974,7 +1333,7 @@ function Breadcrumbs({ className }) {
974
1333
  (path) => {
975
1334
  for (const [id, node] of state.tree.nodesById) {
976
1335
  if (node.path === path) {
977
- actions.selectNode(id);
1336
+ actions.selectAndDrillDown(id);
978
1337
  break;
979
1338
  }
980
1339
  }
@@ -1062,8 +1421,8 @@ function Breadcrumbs({ className }) {
1062
1421
  style: {
1063
1422
  width: "100%",
1064
1423
  boxSizing: "border-box",
1065
- padding: "2px 0",
1066
- fontSize: 12,
1424
+ padding: "3px 0",
1425
+ fontSize: "var(--vj-input-font-size, 13px)",
1067
1426
  fontFamily: "var(--vj-font, monospace)",
1068
1427
  color: "var(--vj-text-muted, #999999)",
1069
1428
  background: "transparent",
@@ -1118,88 +1477,260 @@ function Breadcrumbs({ className }) {
1118
1477
  );
1119
1478
  }
1120
1479
 
1121
- // src/form-view.tsx
1480
+ // src/enum-input.tsx
1481
+ import {
1482
+ useState as useState6,
1483
+ useRef as useRef6,
1484
+ useEffect as useEffect5,
1485
+ useCallback as useCallback5,
1486
+ useMemo as useMemo5
1487
+ } from "react";
1122
1488
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1123
- function getResolvedSchema(schema, rootSchema, path) {
1124
- if (!schema) return void 0;
1125
- const raw = getPropertySchema2(schema, path, rootSchema);
1126
- if (!raw) return void 0;
1127
- return resolveRef(raw, rootSchema ?? schema);
1128
- }
1129
- function getValueColor(node) {
1130
- if (node.type === "boolean" || node.type === "null")
1131
- return "var(--vj-boolean, #569cd6)";
1132
- if (node.type === "number") return "var(--vj-number, #b5cea8)";
1133
- return "var(--vj-string, #ce9178)";
1134
- }
1135
- function getDisplayValue(node) {
1136
- if (node.type === "null") return "null";
1137
- if (node.type === "boolean") return String(node.value);
1138
- if (node.value === null || node.value === void 0) return "";
1139
- return String(node.value);
1140
- }
1141
- function FormField({
1142
- node,
1143
- schema,
1144
- rootSchema,
1145
- depth,
1146
- showDescriptions,
1147
- showCounts,
1148
- formSelectedNodeId,
1149
- editingNodeId,
1150
- collapsedIds,
1151
- maxKeyLength,
1152
- maxDepth,
1153
- isFocused,
1154
- dragState,
1155
- onSelect,
1156
- onToggleCollapse,
1157
- onStartEditing,
1158
- onDragStart,
1159
- onDragOver,
1160
- onDragEnd,
1161
- onDrop
1489
+ var DROPDOWN_MAX_HEIGHT2 = 200;
1490
+ function EnumInput({
1491
+ enumValues,
1492
+ value,
1493
+ onValueChange,
1494
+ inputRef,
1495
+ inputStyle
1162
1496
  }) {
1163
- const { state, actions } = useStudio();
1164
- const isContainer = node.type === "object" || node.type === "array";
1165
- const collapsed = collapsedIds.has(node.id);
1166
- const isSelected = formSelectedNodeId === node.id;
1167
- const isEditing = editingNodeId === node.id;
1168
- const propSchema = getResolvedSchema(schema, rootSchema, node.path);
1169
- const isRequired = checkRequired(node, schema, rootSchema);
1170
- const [hovered, setHovered] = useState6(false);
1171
- const isRoot = node.parentId === null;
1172
- const isDragTarget = dragState.dropTargetNodeId === node.id;
1173
- const isDraggedNode = dragState.draggedNodeId === node.id;
1174
- function handleDragOverEvent(e) {
1175
- e.preventDefault();
1176
- const rect = e.currentTarget.getBoundingClientRect();
1177
- const midY = rect.top + rect.height / 2;
1178
- onDragOver(node.id, e.clientY < midY ? "before" : "after");
1179
- }
1180
- let borderTop = "none";
1181
- let borderBottom = "none";
1182
- if (isDragTarget && dragState.dropPosition === "before") {
1183
- borderTop = "2px solid var(--vj-accent, #007acc)";
1184
- } else if (isDragTarget && dragState.dropPosition === "after") {
1185
- borderBottom = "2px solid var(--vj-accent, #007acc)";
1186
- }
1187
- const valueRef = useRef6(null);
1188
- const keyRef = useRef6(null);
1497
+ const [inputValue, setInputValue] = useState6(value);
1498
+ const [open, setOpen] = useState6(false);
1499
+ const [highlightIndex, setHighlightIndex] = useState6(0);
1500
+ const listRef = useRef6(null);
1501
+ const wrapperRef = useRef6(null);
1189
1502
  useEffect5(() => {
1190
- if (!isEditing) return;
1191
- if (!isContainer) {
1192
- const hasValue = node.value !== null && node.value !== void 0 && node.value !== "";
1193
- if (hasValue && valueRef.current) {
1194
- valueRef.current.focus();
1195
- } else if (keyRef.current) {
1196
- keyRef.current.focus();
1503
+ setInputValue(value);
1504
+ }, [value]);
1505
+ const suggestions = useMemo5(
1506
+ () => enumValues.map((v) => String(v)),
1507
+ [enumValues]
1508
+ );
1509
+ useEffect5(() => {
1510
+ setHighlightIndex(0);
1511
+ }, [suggestions]);
1512
+ const selectValue = useCallback5(
1513
+ (val) => {
1514
+ onValueChange(val);
1515
+ setInputValue(val);
1516
+ setOpen(false);
1517
+ },
1518
+ [onValueChange]
1519
+ );
1520
+ const handleKeyDown = useCallback5(
1521
+ (e) => {
1522
+ if (!open) {
1523
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
1524
+ e.preventDefault();
1525
+ e.stopPropagation();
1526
+ setOpen(true);
1527
+ }
1528
+ return;
1529
+ }
1530
+ switch (e.key) {
1531
+ case "ArrowDown":
1532
+ e.preventDefault();
1533
+ e.stopPropagation();
1534
+ setHighlightIndex((i) => Math.min(i + 1, suggestions.length - 1));
1535
+ break;
1536
+ case "ArrowUp":
1537
+ e.preventDefault();
1538
+ e.stopPropagation();
1539
+ setHighlightIndex((i) => Math.max(i - 1, 0));
1540
+ break;
1541
+ case "Enter":
1542
+ e.preventDefault();
1543
+ e.stopPropagation();
1544
+ if (suggestions.length > 0 && highlightIndex < suggestions.length) {
1545
+ selectValue(suggestions[highlightIndex]);
1546
+ }
1547
+ break;
1548
+ case "Escape":
1549
+ e.preventDefault();
1550
+ e.stopPropagation();
1551
+ setInputValue(value);
1552
+ setOpen(false);
1553
+ break;
1554
+ case "Tab":
1555
+ setInputValue(value);
1556
+ setOpen(false);
1557
+ break;
1558
+ }
1559
+ },
1560
+ [open, suggestions, highlightIndex, value, selectValue]
1561
+ );
1562
+ useEffect5(() => {
1563
+ const el = listRef.current;
1564
+ if (!el || !open) return;
1565
+ const item = el.children[highlightIndex];
1566
+ item?.scrollIntoView({ block: "nearest" });
1567
+ }, [highlightIndex, open]);
1568
+ useEffect5(() => {
1569
+ function handleClickOutside(e) {
1570
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
1571
+ setInputValue(value);
1572
+ setOpen(false);
1573
+ }
1574
+ }
1575
+ document.addEventListener("mousedown", handleClickOutside);
1576
+ return () => document.removeEventListener("mousedown", handleClickOutside);
1577
+ }, [value]);
1578
+ return /* @__PURE__ */ jsxs3(
1579
+ "div",
1580
+ {
1581
+ ref: wrapperRef,
1582
+ style: { position: "relative", flex: 1, minWidth: 0 },
1583
+ children: [
1584
+ /* @__PURE__ */ jsx5(
1585
+ "input",
1586
+ {
1587
+ ref: inputRef,
1588
+ value: inputValue,
1589
+ onChange: (e) => {
1590
+ setInputValue(e.target.value);
1591
+ if (!open) setOpen(true);
1592
+ },
1593
+ onFocus: () => setOpen(true),
1594
+ onKeyDown: handleKeyDown,
1595
+ onClick: (e) => e.stopPropagation(),
1596
+ spellCheck: false,
1597
+ autoComplete: "off",
1598
+ style: inputStyle
1599
+ }
1600
+ ),
1601
+ open && suggestions.length > 0 && /* @__PURE__ */ jsx5(
1602
+ "div",
1603
+ {
1604
+ ref: listRef,
1605
+ style: {
1606
+ position: "absolute",
1607
+ top: "calc(100% + 4px)",
1608
+ left: -32,
1609
+ right: 0,
1610
+ zIndex: 50,
1611
+ maxHeight: DROPDOWN_MAX_HEIGHT2,
1612
+ overflowY: "auto",
1613
+ backgroundColor: "var(--vj-bg-panel, #252526)",
1614
+ border: "1px solid var(--vj-border, #333333)",
1615
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)"
1616
+ },
1617
+ children: suggestions.map((s, i) => /* @__PURE__ */ jsxs3(
1618
+ "div",
1619
+ {
1620
+ onMouseDown: (e) => {
1621
+ e.preventDefault();
1622
+ selectValue(s);
1623
+ },
1624
+ onMouseEnter: () => setHighlightIndex(i),
1625
+ style: {
1626
+ padding: "4px 12px",
1627
+ fontSize: 13,
1628
+ fontFamily: "var(--vj-font, monospace)",
1629
+ display: "flex",
1630
+ alignItems: "center",
1631
+ gap: 6,
1632
+ color: i === highlightIndex ? "var(--vj-text, #cccccc)" : "var(--vj-text-muted, #888888)",
1633
+ backgroundColor: i === highlightIndex ? "var(--vj-bg-hover, #2a2d2e)" : "transparent",
1634
+ cursor: "pointer",
1635
+ whiteSpace: "nowrap",
1636
+ overflow: "hidden",
1637
+ textOverflow: "ellipsis"
1638
+ },
1639
+ children: [
1640
+ /* @__PURE__ */ jsx5("span", { style: { width: 14, flexShrink: 0, fontSize: 12 }, children: s === value ? "\u2713" : "" }),
1641
+ s
1642
+ ]
1643
+ },
1644
+ s
1645
+ ))
1646
+ }
1647
+ )
1648
+ ]
1649
+ }
1650
+ );
1651
+ }
1652
+
1653
+ // src/form-view.tsx
1654
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1655
+ function getResolvedSchema(schema, rootSchema, path) {
1656
+ if (!schema) return void 0;
1657
+ const raw = getPropertySchema(schema, path, rootSchema);
1658
+ if (!raw) return void 0;
1659
+ return resolveRef(raw, rootSchema ?? schema);
1660
+ }
1661
+ function getValueColor(node) {
1662
+ if (node.type === "boolean" || node.type === "null")
1663
+ return "var(--vj-boolean, #569cd6)";
1664
+ if (node.type === "number") return "var(--vj-number, #b5cea8)";
1665
+ return "var(--vj-string, #ce9178)";
1666
+ }
1667
+ function getDisplayValue(node) {
1668
+ if (node.type === "null") return "null";
1669
+ if (node.type === "boolean") return String(node.value);
1670
+ if (node.value === null || node.value === void 0) return "";
1671
+ return String(node.value);
1672
+ }
1673
+ function FormField({
1674
+ node,
1675
+ schema,
1676
+ rootSchema,
1677
+ depth,
1678
+ showDescriptions,
1679
+ showCounts,
1680
+ editingNodeId,
1681
+ collapsedIds,
1682
+ maxKeyLength,
1683
+ maxDepth,
1684
+ isFocused,
1685
+ dragState,
1686
+ onSelect,
1687
+ onToggleCollapse,
1688
+ onStartEditing,
1689
+ onDragStart,
1690
+ onDragOver,
1691
+ onDragEnd,
1692
+ onDrop
1693
+ }) {
1694
+ const { state, actions } = useStudio();
1695
+ const isContainer = node.type === "object" || node.type === "array";
1696
+ const collapsed = collapsedIds.has(node.id);
1697
+ const isSelected = state.selectedNodeIds.has(node.id);
1698
+ const isEditing = editingNodeId === node.id;
1699
+ const propSchema = getResolvedSchema(schema, rootSchema, node.path);
1700
+ const isRequired = checkRequired(node, schema, rootSchema);
1701
+ const [hovered, setHovered] = useState7(false);
1702
+ const isRoot = node.parentId === null;
1703
+ const isDragTarget = dragState.dropTargetNodeId === node.id;
1704
+ const isDraggedNode = dragState.draggedNodeIds.has(node.id);
1705
+ function handleDragOverEvent(e) {
1706
+ e.preventDefault();
1707
+ const rect = e.currentTarget.getBoundingClientRect();
1708
+ const midY = rect.top + rect.height / 2;
1709
+ onDragOver(node.id, e.clientY < midY ? "before" : "after");
1710
+ }
1711
+ let borderTopColor = "transparent";
1712
+ let borderBottomColor = "transparent";
1713
+ if (isDragTarget && dragState.dropPosition === "before") {
1714
+ borderTopColor = "var(--vj-accent, #007acc)";
1715
+ } else if (isDragTarget && dragState.dropPosition === "after") {
1716
+ borderBottomColor = "var(--vj-accent, #007acc)";
1717
+ }
1718
+ const valueRef = useRef7(null);
1719
+ const keyRef = useRef7(null);
1720
+ useEffect6(() => {
1721
+ if (!isEditing) return;
1722
+ if (!isContainer) {
1723
+ const hasValue = node.value !== null && node.value !== void 0 && node.value !== "";
1724
+ if (hasValue && valueRef.current) {
1725
+ valueRef.current.focus();
1726
+ } else if (keyRef.current) {
1727
+ keyRef.current.focus();
1197
1728
  }
1198
1729
  } else if (keyRef.current) {
1199
1730
  keyRef.current.focus();
1200
1731
  }
1201
1732
  }, [isEditing, isContainer, node.value]);
1202
- const handleValueChange = useCallback5(
1733
+ const handleValueChange = useCallback6(
1203
1734
  (newValue) => {
1204
1735
  let parsed;
1205
1736
  if (propSchema?.type === "boolean" || newValue === "true" || newValue === "false") {
@@ -1217,38 +1748,40 @@ function FormField({
1217
1748
  },
1218
1749
  [state.tree, node.id, node.type, actions, propSchema]
1219
1750
  );
1220
- const handleKeyChange = useCallback5(
1751
+ const handleKeyChange = useCallback6(
1221
1752
  (newKey) => {
1222
1753
  const newTree = setKey(state.tree, node.id, newKey);
1223
1754
  actions.setTree(newTree);
1224
1755
  },
1225
1756
  [state.tree, node.id, actions]
1226
1757
  );
1227
- const handleRemove = useCallback5(() => {
1228
- const newTree = removeNode2(state.tree, node.id);
1758
+ const handleRemove = useCallback6(() => {
1759
+ const newTree = removeNode4(state.tree, node.id);
1229
1760
  actions.setTree(newTree);
1230
1761
  }, [state.tree, node.id, actions]);
1231
- const handleAddChild = useCallback5(() => {
1762
+ const handleAddChild = useCallback6(() => {
1232
1763
  const key = node.type === "array" ? String(node.children.length) : `newKey${node.children.length}`;
1233
1764
  const newTree = addProperty(state.tree, node.id, key, "");
1234
1765
  actions.setTree(newTree);
1235
1766
  }, [state.tree, node.id, node.type, node.children.length, actions]);
1236
1767
  const description = propSchema?.description;
1237
- const defaultVal = propSchema?.default;
1238
1768
  const isDeprecated = propSchema?.deprecated;
1239
1769
  const fieldTitle = propSchema?.title;
1240
1770
  const parentIsObject = node.parentId && state.tree.nodesById.get(node.parentId)?.type === "object";
1241
1771
  const rowBg = isSelected ? isFocused ? "var(--vj-bg-selected, #2a5a1e)" : "var(--vj-bg-selected-muted, var(--vj-bg-hover, #2a2d2e))" : hovered ? "var(--vj-bg-hover, #2a2d2e)" : "transparent";
1242
1772
  const rowColor = isSelected && isFocused ? "var(--vj-text-selected, var(--vj-text, #cccccc))" : "var(--vj-text, #cccccc)";
1243
1773
  if (isContainer) {
1244
- return /* @__PURE__ */ jsxs3("div", { children: [
1245
- /* @__PURE__ */ jsxs3(
1774
+ return /* @__PURE__ */ jsxs4("div", { children: [
1775
+ /* @__PURE__ */ jsxs4(
1246
1776
  "div",
1247
1777
  {
1248
1778
  "data-form-node-id": node.id,
1249
1779
  draggable: !isRoot,
1250
1780
  onDragStart: (e) => {
1251
1781
  e.dataTransfer.effectAllowed = "move";
1782
+ if (state.selectedNodeIds.size > 1 && state.selectedNodeIds.has(node.id)) {
1783
+ setMultiDragImage(e, state.selectedNodeIds.size);
1784
+ }
1252
1785
  onDragStart(node.id);
1253
1786
  },
1254
1787
  onDragOver: handleDragOverEvent,
@@ -1261,26 +1794,27 @@ function FormField({
1261
1794
  display: "flex",
1262
1795
  alignItems: "center",
1263
1796
  gap: 6,
1264
- padding: "3px 8px",
1797
+ padding: "1px 8px",
1265
1798
  paddingLeft: 8 + depth * 16,
1266
1799
  cursor: "pointer",
1267
1800
  backgroundColor: rowBg,
1268
1801
  color: rowColor,
1269
1802
  height: 28,
1803
+ boxSizing: "border-box",
1270
1804
  userSelect: "none",
1271
1805
  opacity: isDeprecated ? 0.5 : isDraggedNode ? 0.4 : 1,
1272
- borderTop,
1273
- borderBottom
1806
+ borderTop: `2px solid ${borderTopColor}`,
1807
+ borderBottom: `2px solid ${borderBottomColor}`
1274
1808
  },
1275
1809
  onClick: (e) => {
1276
1810
  e.stopPropagation();
1277
- onSelect(node.id);
1811
+ onSelect(node.id, e);
1278
1812
  },
1279
1813
  onDoubleClick: () => onToggleCollapse(node.id),
1280
1814
  onMouseEnter: () => setHovered(true),
1281
1815
  onMouseLeave: () => setHovered(false),
1282
1816
  children: [
1283
- /* @__PURE__ */ jsx5(
1817
+ /* @__PURE__ */ jsx6(
1284
1818
  "button",
1285
1819
  {
1286
1820
  onClick: (e) => {
@@ -1305,7 +1839,7 @@ function FormField({
1305
1839
  children: "\u25B6"
1306
1840
  }
1307
1841
  ),
1308
- isEditing && !isRoot ? /* @__PURE__ */ jsx5(
1842
+ isEditing && !isRoot ? /* @__PURE__ */ jsx6(
1309
1843
  "input",
1310
1844
  {
1311
1845
  ref: keyRef,
@@ -1317,7 +1851,7 @@ function FormField({
1317
1851
  border: "none",
1318
1852
  color: "inherit",
1319
1853
  fontFamily: "var(--vj-font, monospace)",
1320
- fontSize: 13,
1854
+ fontSize: "var(--vj-input-font-size, 13px)",
1321
1855
  fontWeight: 500,
1322
1856
  padding: 0,
1323
1857
  outline: "none",
@@ -1325,12 +1859,12 @@ function FormField({
1325
1859
  width: `calc(${(maxDepth - depth) * 16}px + ${maxKeyLength}ch)`
1326
1860
  }
1327
1861
  }
1328
- ) : /* @__PURE__ */ jsx5(
1862
+ ) : /* @__PURE__ */ jsx6(
1329
1863
  "span",
1330
1864
  {
1331
1865
  style: {
1332
1866
  color: !isRoot && !parentIsObject && !isSelected ? "var(--vj-text-muted, #888888)" : "inherit",
1333
- fontSize: 13,
1867
+ fontSize: "var(--vj-input-font-size, 13px)",
1334
1868
  fontFamily: "var(--vj-font, monospace)",
1335
1869
  fontWeight: 500,
1336
1870
  flexShrink: 0,
@@ -1340,7 +1874,7 @@ function FormField({
1340
1874
  children: isRoot ? "/" : getDisplayKey(node, state.tree)
1341
1875
  }
1342
1876
  ),
1343
- showDescriptions && fieldTitle && !isSelected && /* @__PURE__ */ jsx5(
1877
+ showDescriptions && fieldTitle && !isSelected && /* @__PURE__ */ jsx6(
1344
1878
  "span",
1345
1879
  {
1346
1880
  style: {
@@ -1351,7 +1885,7 @@ function FormField({
1351
1885
  children: fieldTitle
1352
1886
  }
1353
1887
  ),
1354
- hovered && /* @__PURE__ */ jsxs3(
1888
+ hovered && /* @__PURE__ */ jsxs4(
1355
1889
  "button",
1356
1890
  {
1357
1891
  onClick: (e) => {
@@ -1373,7 +1907,7 @@ function FormField({
1373
1907
  ]
1374
1908
  }
1375
1909
  ),
1376
- showCounts && /* @__PURE__ */ jsx5(
1910
+ showCounts && /* @__PURE__ */ jsx6(
1377
1911
  "span",
1378
1912
  {
1379
1913
  style: {
@@ -1385,7 +1919,7 @@ function FormField({
1385
1919
  children: node.type === "array" ? `${node.children.length} items` : `${node.children.length} properties`
1386
1920
  }
1387
1921
  ),
1388
- !isRoot && isEditing && /* @__PURE__ */ jsx5(
1922
+ !isRoot && isEditing && /* @__PURE__ */ jsx6(
1389
1923
  "button",
1390
1924
  {
1391
1925
  onClick: (e) => {
@@ -1409,7 +1943,7 @@ function FormField({
1409
1943
  ]
1410
1944
  }
1411
1945
  ),
1412
- showDescriptions && description && /* @__PURE__ */ jsx5(
1946
+ showDescriptions && description && /* @__PURE__ */ jsx6(
1413
1947
  "div",
1414
1948
  {
1415
1949
  style: {
@@ -1422,7 +1956,7 @@ function FormField({
1422
1956
  children: description
1423
1957
  }
1424
1958
  ),
1425
- !collapsed && /* @__PURE__ */ jsx5("div", { children: node.children.map((child) => /* @__PURE__ */ jsx5(
1959
+ !collapsed && /* @__PURE__ */ jsx6("div", { children: node.children.map((child) => /* @__PURE__ */ jsx6(
1426
1960
  FormField,
1427
1961
  {
1428
1962
  node: child,
@@ -1431,7 +1965,6 @@ function FormField({
1431
1965
  depth: depth + 1,
1432
1966
  showDescriptions,
1433
1967
  showCounts,
1434
- formSelectedNodeId,
1435
1968
  editingNodeId,
1436
1969
  collapsedIds,
1437
1970
  maxKeyLength,
@@ -1452,13 +1985,16 @@ function FormField({
1452
1985
  }
1453
1986
  const displayValue = getDisplayValue(node);
1454
1987
  const valueColor = getValueColor(node);
1455
- return /* @__PURE__ */ jsxs3(
1988
+ return /* @__PURE__ */ jsxs4(
1456
1989
  "div",
1457
1990
  {
1458
1991
  "data-form-node-id": node.id,
1459
1992
  draggable: !isRoot,
1460
1993
  onDragStart: (e) => {
1461
1994
  e.dataTransfer.effectAllowed = "move";
1995
+ if (state.selectedNodeIds.size > 1 && state.selectedNodeIds.has(node.id)) {
1996
+ setMultiDragImage(e, state.selectedNodeIds.size);
1997
+ }
1462
1998
  onDragStart(node.id);
1463
1999
  },
1464
2000
  onDragOver: handleDragOverEvent,
@@ -1471,27 +2007,28 @@ function FormField({
1471
2007
  display: "flex",
1472
2008
  alignItems: "center",
1473
2009
  gap: 6,
1474
- padding: "3px 8px",
2010
+ padding: "1px 8px",
1475
2011
  paddingLeft: 8 + depth * 16,
1476
2012
  cursor: "pointer",
1477
2013
  backgroundColor: rowBg,
1478
2014
  color: rowColor,
1479
2015
  height: 28,
2016
+ boxSizing: "border-box",
1480
2017
  userSelect: "none",
1481
2018
  opacity: isDeprecated ? 0.5 : isDraggedNode ? 0.4 : 1,
1482
- borderTop,
1483
- borderBottom
2019
+ borderTop: `2px solid ${borderTopColor}`,
2020
+ borderBottom: `2px solid ${borderBottomColor}`
1484
2021
  },
1485
2022
  onClick: (e) => {
1486
2023
  e.stopPropagation();
1487
- onSelect(node.id);
2024
+ onSelect(node.id, e);
1488
2025
  },
1489
2026
  onDoubleClick: () => onStartEditing(node.id),
1490
2027
  onMouseEnter: () => setHovered(true),
1491
2028
  onMouseLeave: () => setHovered(false),
1492
2029
  children: [
1493
- /* @__PURE__ */ jsx5("span", { style: { width: 16, flexShrink: 0 } }),
1494
- isEditing && parentIsObject ? /* @__PURE__ */ jsx5(
2030
+ /* @__PURE__ */ jsx6("span", { style: { width: 16, flexShrink: 0 } }),
2031
+ isEditing && parentIsObject ? /* @__PURE__ */ jsx6(
1495
2032
  "input",
1496
2033
  {
1497
2034
  ref: keyRef,
@@ -1509,19 +2046,19 @@ function FormField({
1509
2046
  border: "none",
1510
2047
  color: "inherit",
1511
2048
  fontFamily: "var(--vj-font, monospace)",
1512
- fontSize: 13,
2049
+ fontSize: "var(--vj-input-font-size, 13px)",
1513
2050
  padding: 0,
1514
2051
  flexShrink: 0,
1515
2052
  outline: "none",
1516
2053
  width: `calc(${(maxDepth - depth) * 16}px + ${maxKeyLength}ch)`
1517
2054
  }
1518
2055
  }
1519
- ) : /* @__PURE__ */ jsx5(
2056
+ ) : /* @__PURE__ */ jsx6(
1520
2057
  "span",
1521
2058
  {
1522
2059
  style: {
1523
2060
  color: !parentIsObject && !isSelected ? "var(--vj-text-muted, #888888)" : "inherit",
1524
- fontSize: 13,
2061
+ fontSize: "var(--vj-input-font-size, 13px)",
1525
2062
  fontFamily: "var(--vj-font, monospace)",
1526
2063
  flexShrink: 0,
1527
2064
  display: "inline-block",
@@ -1530,7 +2067,7 @@ function FormField({
1530
2067
  children: getDisplayKey(node, state.tree)
1531
2068
  }
1532
2069
  ),
1533
- isRequired && !isSelected && /* @__PURE__ */ jsx5(
2070
+ isRequired && !isSelected && /* @__PURE__ */ jsx6(
1534
2071
  "span",
1535
2072
  {
1536
2073
  style: {
@@ -1541,7 +2078,7 @@ function FormField({
1541
2078
  children: "*"
1542
2079
  }
1543
2080
  ),
1544
- isEditing ? /* @__PURE__ */ jsx5(
2081
+ isEditing ? /* @__PURE__ */ jsx6(
1545
2082
  "div",
1546
2083
  {
1547
2084
  style: {
@@ -1560,12 +2097,12 @@ function FormField({
1560
2097
  valueColor
1561
2098
  )
1562
2099
  }
1563
- ) : /* @__PURE__ */ jsx5(
2100
+ ) : /* @__PURE__ */ jsx6(
1564
2101
  "span",
1565
2102
  {
1566
2103
  style: {
1567
2104
  color: valueColor,
1568
- fontSize: 13,
2105
+ fontSize: "var(--vj-input-font-size, 13px)",
1569
2106
  fontFamily: "var(--vj-font, monospace)",
1570
2107
  overflow: "hidden",
1571
2108
  textOverflow: "ellipsis",
@@ -1575,7 +2112,7 @@ function FormField({
1575
2112
  children: displayValue
1576
2113
  }
1577
2114
  ),
1578
- isEditing && /* @__PURE__ */ jsx5(
2115
+ isEditing && /* @__PURE__ */ jsx6(
1579
2116
  "button",
1580
2117
  {
1581
2118
  onClick: (e) => {
@@ -1606,54 +2143,44 @@ function renderEditInput(node, propSchema, displayValue, handleValueChange, inpu
1606
2143
  background: "none",
1607
2144
  border: "none",
1608
2145
  fontFamily: "var(--vj-font, monospace)",
1609
- fontSize: 13,
2146
+ fontSize: "var(--vj-input-font-size, 13px)",
1610
2147
  padding: 0,
1611
2148
  flex: 1,
1612
2149
  outline: "none",
1613
2150
  color: valueColor
1614
2151
  };
1615
2152
  if (node.type === "boolean") {
1616
- return /* @__PURE__ */ jsxs3(
1617
- "select",
2153
+ return /* @__PURE__ */ jsx6(
2154
+ EnumInput,
1618
2155
  {
1619
- ref: inputRef,
2156
+ enumValues: ["true", "false"],
1620
2157
  value: String(node.value),
1621
- onChange: (e) => handleValueChange(e.target.value),
1622
- onClick: (e) => e.stopPropagation(),
1623
- style: { ...inputStyle, cursor: "pointer" },
1624
- children: [
1625
- /* @__PURE__ */ jsx5("option", { value: "true", children: "true" }),
1626
- /* @__PURE__ */ jsx5("option", { value: "false", children: "false" })
1627
- ]
2158
+ onValueChange: handleValueChange,
2159
+ inputRef,
2160
+ inputStyle
1628
2161
  }
1629
2162
  );
1630
2163
  }
1631
2164
  if (hasEnumValues && propSchema?.enum) {
1632
- return /* @__PURE__ */ jsxs3(
1633
- "select",
2165
+ return /* @__PURE__ */ jsx6(
2166
+ EnumInput,
1634
2167
  {
1635
- ref: inputRef,
2168
+ enumValues: propSchema.enum,
1636
2169
  value: displayValue,
1637
- onChange: (e) => handleValueChange(e.target.value),
1638
- onClick: (e) => e.stopPropagation(),
1639
- style: { ...inputStyle, cursor: "pointer" },
1640
- children: [
1641
- !propSchema.enum.some(
1642
- (v) => JSON.stringify(v) === JSON.stringify(node.value)
1643
- ) && /* @__PURE__ */ jsx5("option", { value: displayValue, children: displayValue || "(empty)" }),
1644
- propSchema.enum.map((v, i) => /* @__PURE__ */ jsx5("option", { value: String(v), children: String(v) }, i))
1645
- ]
2170
+ onValueChange: handleValueChange,
2171
+ inputRef,
2172
+ inputStyle
1646
2173
  }
1647
2174
  );
1648
2175
  }
1649
2176
  if (node.type === "null") {
1650
- return /* @__PURE__ */ jsx5(
2177
+ return /* @__PURE__ */ jsx6(
1651
2178
  "span",
1652
2179
  {
1653
2180
  style: {
1654
2181
  color: "var(--vj-boolean, #569cd6)",
1655
2182
  fontFamily: "var(--vj-font, monospace)",
1656
- fontSize: 13,
2183
+ fontSize: "var(--vj-input-font-size, 13px)",
1657
2184
  fontStyle: "italic",
1658
2185
  flex: 1
1659
2186
  },
@@ -1661,7 +2188,7 @@ function renderEditInput(node, propSchema, displayValue, handleValueChange, inpu
1661
2188
  }
1662
2189
  );
1663
2190
  }
1664
- return /* @__PURE__ */ jsx5(
2191
+ return /* @__PURE__ */ jsx6(
1665
2192
  "input",
1666
2193
  {
1667
2194
  ref: inputRef,
@@ -1686,35 +2213,35 @@ function FormView({
1686
2213
  }) {
1687
2214
  const { state, actions } = useStudio();
1688
2215
  const rootSchema = state.schema ?? void 0;
1689
- const selectedNode = state.selectedNodeId ? state.tree.nodesById.get(state.selectedNodeId) : null;
1690
- const displayNode = selectedNode ?? state.tree.root;
1691
- const [formSelectedNodeId, setFormSelectedNodeId] = useState6(
1692
- null
1693
- );
1694
- const [editingNodeId, setEditingNodeId] = useState6(null);
1695
- const preEditTreeRef = useRef6(null);
1696
- const [collapsedIds, setCollapsedIds] = useState6(
2216
+ const drillDownNode = state.drillDownNodeId ? state.tree.nodesById.get(state.drillDownNodeId) : null;
2217
+ const displayNode = drillDownNode ?? state.tree.root;
2218
+ const [editingNodeId, setEditingNodeId] = useState7(null);
2219
+ const preEditTreeRef = useRef7(null);
2220
+ const [collapsedIds, setCollapsedIds] = useState7(
1697
2221
  () => /* @__PURE__ */ new Set()
1698
2222
  );
1699
- const containerRef = useRef6(null);
1700
- const [isFocused, setIsFocused] = useState6(false);
1701
- const {
1702
- dragState,
1703
- handleDragStart,
1704
- handleDragOver,
1705
- handleDragEnd,
1706
- handleDrop
1707
- } = useDragDrop();
1708
- useEffect5(() => {
1709
- setFormSelectedNodeId(null);
2223
+ const containerRef = useRef7(null);
2224
+ const [isFocused, setIsFocused] = useState7(false);
2225
+ useEffect6(() => {
1710
2226
  setEditingNodeId(null);
1711
2227
  setCollapsedIds(/* @__PURE__ */ new Set());
1712
2228
  }, [displayNode.id]);
1713
- const visibleNodes = useMemo4(
2229
+ const visibleNodes = useMemo6(
1714
2230
  () => getVisibleNodes(displayNode, (id) => !collapsedIds.has(id)),
1715
2231
  [displayNode, collapsedIds]
1716
2232
  );
1717
- const { maxKeyLength, maxDepth } = useMemo4(() => {
2233
+ const {
2234
+ dragState,
2235
+ handleDragStart,
2236
+ handleDragOver,
2237
+ handleDragEnd,
2238
+ handleDrop
2239
+ } = useDragDrop(visibleNodes, state.selectedNodeIds);
2240
+ useEffect6(() => {
2241
+ actions.setVisibleNodesOverride(visibleNodes);
2242
+ return () => actions.setVisibleNodesOverride(null);
2243
+ }, [visibleNodes, actions]);
2244
+ const { maxKeyLength, maxDepth } = useMemo6(() => {
1718
2245
  let maxKey = 1;
1719
2246
  let maxD = 0;
1720
2247
  const baseSegments = displayNode.path === "/" ? 0 : displayNode.path.split("/").filter(Boolean).length;
@@ -1727,11 +2254,21 @@ function FormView({
1727
2254
  }
1728
2255
  return { maxKeyLength: maxKey, maxDepth: maxD };
1729
2256
  }, [visibleNodes, displayNode.path, state.tree]);
1730
- const handleSelect = useCallback5((nodeId) => {
1731
- setFormSelectedNodeId(nodeId);
1732
- setEditingNodeId(null);
1733
- }, []);
1734
- const handleToggleCollapse = useCallback5((nodeId) => {
2257
+ const handleSelect = useCallback6(
2258
+ (nodeId, e) => {
2259
+ setEditingNodeId(null);
2260
+ if (e.shiftKey) {
2261
+ actions.setVisibleNodesOverride(visibleNodes);
2262
+ actions.selectNodeRange(nodeId);
2263
+ } else if (e.metaKey || e.ctrlKey) {
2264
+ actions.toggleNodeSelection(nodeId);
2265
+ } else {
2266
+ actions.selectNode(nodeId);
2267
+ }
2268
+ },
2269
+ [actions, visibleNodes]
2270
+ );
2271
+ const handleToggleCollapse = useCallback6((nodeId) => {
1735
2272
  setCollapsedIds((prev) => {
1736
2273
  const next = new Set(prev);
1737
2274
  if (next.has(nodeId)) {
@@ -1742,14 +2279,14 @@ function FormView({
1742
2279
  return next;
1743
2280
  });
1744
2281
  }, []);
1745
- const handleStartEditing = useCallback5(
2282
+ const handleStartEditing = useCallback6(
1746
2283
  (nodeId) => {
1747
2284
  preEditTreeRef.current = state.tree;
1748
2285
  setEditingNodeId(nodeId);
1749
2286
  },
1750
2287
  [state.tree]
1751
2288
  );
1752
- const scrollToNode = useCallback5((nodeId) => {
2289
+ const scrollToNode = useCallback6((nodeId) => {
1753
2290
  requestAnimationFrame(() => {
1754
2291
  const el = containerRef.current?.querySelector(
1755
2292
  `[data-form-node-id="${nodeId}"]`
@@ -1757,7 +2294,7 @@ function FormView({
1757
2294
  el?.scrollIntoView({ block: "nearest" });
1758
2295
  });
1759
2296
  }, []);
1760
- const handleKeyDown = useCallback5(
2297
+ const handleKeyDown = useCallback6(
1761
2298
  (e) => {
1762
2299
  if (editingNodeId) {
1763
2300
  if (e.key === "Escape") {
@@ -1778,15 +2315,23 @@ function FormView({
1778
2315
  }
1779
2316
  return;
1780
2317
  }
1781
- const currentIndex = visibleNodes.findIndex(
1782
- (n) => n.id === formSelectedNodeId
2318
+ let currentIndex = visibleNodes.findIndex(
2319
+ (n) => n.id === state.focusedNodeId
1783
2320
  );
2321
+ if (currentIndex === -1 && visibleNodes.length > 0) {
2322
+ currentIndex = 0;
2323
+ }
1784
2324
  switch (e.key) {
1785
2325
  case "ArrowDown": {
1786
2326
  e.preventDefault();
1787
2327
  const next = visibleNodes[currentIndex + 1];
1788
2328
  if (next) {
1789
- setFormSelectedNodeId(next.id);
2329
+ if (e.shiftKey) {
2330
+ actions.setVisibleNodesOverride(visibleNodes);
2331
+ actions.selectNodeRange(next.id);
2332
+ } else {
2333
+ actions.selectNode(next.id);
2334
+ }
1790
2335
  scrollToNode(next.id);
1791
2336
  }
1792
2337
  break;
@@ -1795,7 +2340,12 @@ function FormView({
1795
2340
  e.preventDefault();
1796
2341
  const prev = visibleNodes[currentIndex - 1];
1797
2342
  if (prev) {
1798
- setFormSelectedNodeId(prev.id);
2343
+ if (e.shiftKey) {
2344
+ actions.setVisibleNodesOverride(visibleNodes);
2345
+ actions.selectNodeRange(prev.id);
2346
+ } else {
2347
+ actions.selectNode(prev.id);
2348
+ }
1799
2349
  scrollToNode(prev.id);
1800
2350
  }
1801
2351
  break;
@@ -1811,7 +2361,7 @@ function FormView({
1811
2361
  return next;
1812
2362
  });
1813
2363
  } else if (node.children.length > 0) {
1814
- setFormSelectedNodeId(node.children[0].id);
2364
+ actions.selectNode(node.children[0].id);
1815
2365
  scrollToNode(node.children[0].id);
1816
2366
  }
1817
2367
  }
@@ -1833,7 +2383,7 @@ function FormView({
1833
2383
  (n) => n.id === current.parentId
1834
2384
  );
1835
2385
  if (parentInVisible) {
1836
- setFormSelectedNodeId(parentInVisible.id);
2386
+ actions.selectNode(parentInVisible.id);
1837
2387
  scrollToNode(parentInVisible.id);
1838
2388
  }
1839
2389
  }
@@ -1841,30 +2391,54 @@ function FormView({
1841
2391
  }
1842
2392
  case "Enter": {
1843
2393
  e.preventDefault();
1844
- if (formSelectedNodeId) {
2394
+ if (state.focusedNodeId) {
1845
2395
  preEditTreeRef.current = state.tree;
1846
- setEditingNodeId(formSelectedNodeId);
2396
+ actions.selectNode(state.focusedNodeId);
2397
+ setEditingNodeId(state.focusedNodeId);
2398
+ }
2399
+ break;
2400
+ }
2401
+ case "a": {
2402
+ if (e.metaKey || e.ctrlKey) {
2403
+ e.preventDefault();
2404
+ const ids = computeSelectAllIds(
2405
+ state.tree,
2406
+ state.focusedNodeId,
2407
+ state.selectedNodeIds
2408
+ );
2409
+ if (ids) {
2410
+ actions.setSelection(
2411
+ state.focusedNodeId,
2412
+ ids,
2413
+ state.focusedNodeId
2414
+ );
2415
+ }
1847
2416
  }
1848
2417
  break;
1849
2418
  }
1850
2419
  case "Escape": {
1851
2420
  e.preventDefault();
1852
- setEditingNodeId(null);
2421
+ if (state.selectedNodeIds.size > 1 && state.focusedNodeId) {
2422
+ actions.selectNode(state.focusedNodeId);
2423
+ } else {
2424
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
2425
+ }
1853
2426
  break;
1854
2427
  }
1855
2428
  case "Delete":
1856
2429
  case "Backspace": {
1857
2430
  e.preventDefault();
1858
- const toDelete = currentIndex >= 0 ? visibleNodes[currentIndex] : null;
1859
- if (toDelete && toDelete.parentId) {
1860
- const nextSelect = visibleNodes[currentIndex + 1] ?? visibleNodes[currentIndex - 1];
1861
- const newTree = removeNode2(state.tree, toDelete.id);
1862
- actions.setTree(newTree);
1863
- if (nextSelect && nextSelect.id !== toDelete.id) {
1864
- setFormSelectedNodeId(nextSelect.id);
1865
- } else {
1866
- setFormSelectedNodeId(null);
1867
- }
2431
+ const { newTree, nextFocusId } = deleteSelectedNodes(
2432
+ state.tree,
2433
+ state.selectedNodeIds,
2434
+ visibleNodes
2435
+ );
2436
+ if (newTree === state.tree) break;
2437
+ actions.setTree(newTree);
2438
+ if (nextFocusId) {
2439
+ actions.selectNode(nextFocusId);
2440
+ } else {
2441
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
1868
2442
  }
1869
2443
  break;
1870
2444
  }
@@ -1872,7 +2446,8 @@ function FormView({
1872
2446
  },
1873
2447
  [
1874
2448
  visibleNodes,
1875
- formSelectedNodeId,
2449
+ state.focusedNodeId,
2450
+ state.selectedNodeIds,
1876
2451
  editingNodeId,
1877
2452
  collapsedIds,
1878
2453
  scrollToNode,
@@ -1880,7 +2455,7 @@ function FormView({
1880
2455
  actions
1881
2456
  ]
1882
2457
  );
1883
- return /* @__PURE__ */ jsxs3(
2458
+ return /* @__PURE__ */ jsxs4(
1884
2459
  "div",
1885
2460
  {
1886
2461
  className,
@@ -1893,19 +2468,21 @@ function FormView({
1893
2468
  flexDirection: "column"
1894
2469
  },
1895
2470
  children: [
1896
- /* @__PURE__ */ jsx5(
2471
+ /* @__PURE__ */ jsx6(
1897
2472
  "div",
1898
2473
  {
1899
2474
  style: {
2475
+ display: "flex",
2476
+ alignItems: "center",
1900
2477
  padding: "4px 8px",
1901
2478
  borderBottom: "1px solid var(--vj-border, #333333)",
1902
- backgroundColor: "var(--vj-bg-panel, #252526)",
2479
+ backgroundColor: "var(--vj-bg, #1e1e1e)",
1903
2480
  flexShrink: 0
1904
2481
  },
1905
- children: /* @__PURE__ */ jsx5(Breadcrumbs, {})
2482
+ children: /* @__PURE__ */ jsx6(Breadcrumbs, {})
1906
2483
  }
1907
2484
  ),
1908
- /* @__PURE__ */ jsx5(
2485
+ /* @__PURE__ */ jsx6(
1909
2486
  "div",
1910
2487
  {
1911
2488
  ref: containerRef,
@@ -1923,7 +2500,7 @@ function FormView({
1923
2500
  overflow: "auto",
1924
2501
  outline: "none"
1925
2502
  },
1926
- children: /* @__PURE__ */ jsx5(
2503
+ children: /* @__PURE__ */ jsx6(
1927
2504
  FormField,
1928
2505
  {
1929
2506
  node: displayNode,
@@ -1932,7 +2509,6 @@ function FormView({
1932
2509
  depth: 0,
1933
2510
  showDescriptions,
1934
2511
  showCounts,
1935
- formSelectedNodeId,
1936
2512
  editingNodeId,
1937
2513
  collapsedIds,
1938
2514
  maxKeyLength,
@@ -1956,12 +2532,12 @@ function FormView({
1956
2532
  }
1957
2533
 
1958
2534
  // src/search-bar.tsx
1959
- import { useRef as useRef7, useEffect as useEffect6, useCallback as useCallback6 } from "react";
1960
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2535
+ import { useRef as useRef8, useEffect as useEffect7, useCallback as useCallback7 } from "react";
2536
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1961
2537
  function SearchBar({ className }) {
1962
2538
  const { state, actions } = useStudio();
1963
- const inputRef = useRef7(null);
1964
- const handleKeyDown = useCallback6(
2539
+ const inputRef = useRef8(null);
2540
+ const handleKeyDown = useCallback7(
1965
2541
  (e) => {
1966
2542
  if (e.key === "Enter") {
1967
2543
  e.preventDefault();
@@ -1979,7 +2555,7 @@ function SearchBar({ className }) {
1979
2555
  },
1980
2556
  [actions]
1981
2557
  );
1982
- useEffect6(() => {
2558
+ useEffect7(() => {
1983
2559
  function handleGlobalKeyDown(e) {
1984
2560
  const mod = e.metaKey || e.ctrlKey;
1985
2561
  if (mod && e.key === "f") {
@@ -1993,7 +2569,7 @@ function SearchBar({ className }) {
1993
2569
  }, []);
1994
2570
  const matchCount = state.searchMatches.length;
1995
2571
  const currentMatch = matchCount > 0 ? state.searchMatchIndex + 1 : 0;
1996
- return /* @__PURE__ */ jsxs4(
2572
+ return /* @__PURE__ */ jsxs5(
1997
2573
  "div",
1998
2574
  {
1999
2575
  className,
@@ -2002,11 +2578,11 @@ function SearchBar({ className }) {
2002
2578
  alignItems: "center",
2003
2579
  gap: 6,
2004
2580
  padding: "4px 8px",
2005
- backgroundColor: "var(--vj-bg-panel, #252526)",
2581
+ backgroundColor: "var(--vj-bg, #1e1e1e)",
2006
2582
  borderBottom: "1px solid var(--vj-border, #333333)"
2007
2583
  },
2008
2584
  children: [
2009
- /* @__PURE__ */ jsx6(
2585
+ /* @__PURE__ */ jsx7(
2010
2586
  "input",
2011
2587
  {
2012
2588
  ref: inputRef,
@@ -2022,14 +2598,14 @@ function SearchBar({ className }) {
2022
2598
  borderRadius: 3,
2023
2599
  color: "var(--vj-text, #cccccc)",
2024
2600
  fontFamily: "var(--vj-font, monospace)",
2025
- fontSize: 12,
2601
+ fontSize: "var(--vj-input-font-size, 13px)",
2026
2602
  padding: "3px 8px",
2027
2603
  outline: "none",
2028
2604
  minWidth: 0
2029
2605
  }
2030
2606
  }
2031
2607
  ),
2032
- /* @__PURE__ */ jsx6(
2608
+ /* @__PURE__ */ jsx7(
2033
2609
  "div",
2034
2610
  {
2035
2611
  style: {
@@ -2039,8 +2615,8 @@ function SearchBar({ className }) {
2039
2615
  flexShrink: 0,
2040
2616
  height: 18
2041
2617
  },
2042
- children: state.searchQuery ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
2043
- /* @__PURE__ */ jsx6(
2618
+ children: state.searchQuery ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
2619
+ /* @__PURE__ */ jsx7(
2044
2620
  "span",
2045
2621
  {
2046
2622
  style: {
@@ -2053,7 +2629,7 @@ function SearchBar({ className }) {
2053
2629
  children: matchCount > 0 ? `${currentMatch}/${matchCount}` : "0/0"
2054
2630
  }
2055
2631
  ),
2056
- /* @__PURE__ */ jsx6(
2632
+ /* @__PURE__ */ jsx7(
2057
2633
  "button",
2058
2634
  {
2059
2635
  onClick: actions.prevSearchMatch,
@@ -2077,7 +2653,7 @@ function SearchBar({ className }) {
2077
2653
  children: "\u25B2"
2078
2654
  }
2079
2655
  ),
2080
- /* @__PURE__ */ jsx6(
2656
+ /* @__PURE__ */ jsx7(
2081
2657
  "button",
2082
2658
  {
2083
2659
  onClick: actions.nextSearchMatch,
@@ -2101,7 +2677,7 @@ function SearchBar({ className }) {
2101
2677
  children: "\u25BC"
2102
2678
  }
2103
2679
  ),
2104
- /* @__PURE__ */ jsx6(
2680
+ /* @__PURE__ */ jsx7(
2105
2681
  "button",
2106
2682
  {
2107
2683
  onClick: () => actions.setSearchQuery(""),
@@ -2124,8 +2700,8 @@ function SearchBar({ className }) {
2124
2700
  children: "\xD7"
2125
2701
  }
2126
2702
  )
2127
- ] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
2128
- /* @__PURE__ */ jsx6(
2703
+ ] }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
2704
+ /* @__PURE__ */ jsx7(
2129
2705
  "button",
2130
2706
  {
2131
2707
  onClick: actions.expandAll,
@@ -2142,7 +2718,7 @@ function SearchBar({ className }) {
2142
2718
  alignItems: "center"
2143
2719
  },
2144
2720
  title: "Expand all",
2145
- children: /* @__PURE__ */ jsxs4(
2721
+ children: /* @__PURE__ */ jsxs5(
2146
2722
  "svg",
2147
2723
  {
2148
2724
  width: "14",
@@ -2154,15 +2730,15 @@ function SearchBar({ className }) {
2154
2730
  strokeLinecap: "round",
2155
2731
  strokeLinejoin: "round",
2156
2732
  children: [
2157
- /* @__PURE__ */ jsx6("path", { d: "M8 2v4M5 4l3-2 3 2" }),
2158
- /* @__PURE__ */ jsx6("path", { d: "M8 14v-4M5 12l3 2 3-2" }),
2159
- /* @__PURE__ */ jsx6("path", { d: "M2 8h12" })
2733
+ /* @__PURE__ */ jsx7("path", { d: "M8 2v4M5 4l3-2 3 2" }),
2734
+ /* @__PURE__ */ jsx7("path", { d: "M8 14v-4M5 12l3 2 3-2" }),
2735
+ /* @__PURE__ */ jsx7("path", { d: "M2 8h12" })
2160
2736
  ]
2161
2737
  }
2162
2738
  )
2163
2739
  }
2164
2740
  ),
2165
- /* @__PURE__ */ jsx6(
2741
+ /* @__PURE__ */ jsx7(
2166
2742
  "button",
2167
2743
  {
2168
2744
  onClick: actions.collapseAll,
@@ -2179,7 +2755,7 @@ function SearchBar({ className }) {
2179
2755
  alignItems: "center"
2180
2756
  },
2181
2757
  title: "Collapse all",
2182
- children: /* @__PURE__ */ jsxs4(
2758
+ children: /* @__PURE__ */ jsxs5(
2183
2759
  "svg",
2184
2760
  {
2185
2761
  width: "14",
@@ -2191,9 +2767,9 @@ function SearchBar({ className }) {
2191
2767
  strokeLinecap: "round",
2192
2768
  strokeLinejoin: "round",
2193
2769
  children: [
2194
- /* @__PURE__ */ jsx6("path", { d: "M8 5V1M5 3l3 2 3-2" }),
2195
- /* @__PURE__ */ jsx6("path", { d: "M8 11v4M5 13l3-2 3 2" }),
2196
- /* @__PURE__ */ jsx6("path", { d: "M2 8h12" })
2770
+ /* @__PURE__ */ jsx7("path", { d: "M8 5V1M5 3l3 2 3-2" }),
2771
+ /* @__PURE__ */ jsx7("path", { d: "M8 11v4M5 13l3-2 3 2" }),
2772
+ /* @__PURE__ */ jsx7("path", { d: "M2 8h12" })
2197
2773
  ]
2198
2774
  }
2199
2775
  )
@@ -2208,31 +2784,7 @@ function SearchBar({ className }) {
2208
2784
  }
2209
2785
 
2210
2786
  // src/json-editor.tsx
2211
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2212
- var DEFAULT_CSS_VARS = {
2213
- "--vj-bg": "#1e1e1e",
2214
- "--vj-bg-panel": "#252526",
2215
- "--vj-bg-hover": "#2a2d2e",
2216
- "--vj-bg-selected": "#2a5a1e",
2217
- "--vj-bg-selected-muted": "#2a2d2e",
2218
- "--vj-bg-match": "#3a3520",
2219
- "--vj-bg-match-active": "#51502b",
2220
- "--vj-border": "#333333",
2221
- "--vj-border-subtle": "#2a2a2a",
2222
- "--vj-text": "#cccccc",
2223
- "--vj-text-muted": "#888888",
2224
- "--vj-text-dim": "#666666",
2225
- "--vj-text-dimmer": "#555555",
2226
- "--vj-string": "#ce9178",
2227
- "--vj-number": "#b5cea8",
2228
- "--vj-boolean": "#569cd6",
2229
- "--vj-accent": "#007acc",
2230
- "--vj-accent-muted": "#094771",
2231
- "--vj-input-bg": "#3c3c3c",
2232
- "--vj-input-border": "#555555",
2233
- "--vj-error": "#f48771",
2234
- "--vj-font": "monospace"
2235
- };
2787
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2236
2788
  function JsonEditor({
2237
2789
  value,
2238
2790
  defaultValue,
@@ -2251,15 +2803,15 @@ function JsonEditor({
2251
2803
  }) {
2252
2804
  const isControlled = value !== void 0;
2253
2805
  const initialValue = isControlled ? value : defaultValue ?? {};
2254
- const [editorKey, setEditorKey] = useState7(0);
2255
- const valueRef = useRef8(initialValue);
2256
- useEffect7(() => {
2806
+ const [editorKey, setEditorKey] = useState8(0);
2807
+ const valueRef = useRef9(initialValue);
2808
+ useEffect8(() => {
2257
2809
  if (isControlled && value !== valueRef.current) {
2258
2810
  valueRef.current = value;
2259
2811
  setEditorKey((k) => k + 1);
2260
2812
  }
2261
2813
  }, [value, isControlled]);
2262
- const handleChange = useCallback7(
2814
+ const handleChange = useCallback8(
2263
2815
  (newValue) => {
2264
2816
  valueRef.current = newValue;
2265
2817
  if (!readOnly) {
@@ -2277,25 +2829,35 @@ function JsonEditor({
2277
2829
  ...DEFAULT_CSS_VARS,
2278
2830
  ...style
2279
2831
  };
2280
- return /* @__PURE__ */ jsx7("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx7(
2281
- VisualJson,
2282
- {
2283
- value: valueRef.current,
2284
- onChange: readOnly ? void 0 : handleChange,
2285
- schema,
2286
- children: /* @__PURE__ */ jsx7(
2287
- EditorLayout,
2288
- {
2289
- treeShowValues,
2290
- treeShowCounts,
2291
- editorShowDescriptions,
2292
- editorShowCounts,
2293
- sidebarOpen
2832
+ return /* @__PURE__ */ jsxs6("div", { className, "data-vj-root": "", style: containerStyle, children: [
2833
+ /* @__PURE__ */ jsx8(
2834
+ "style",
2835
+ {
2836
+ dangerouslySetInnerHTML: {
2837
+ __html: `@media(pointer:coarse){[data-vj-root]{--vj-input-font-size:16px}}`
2294
2838
  }
2295
- )
2296
- },
2297
- editorKey
2298
- ) });
2839
+ }
2840
+ ),
2841
+ /* @__PURE__ */ jsx8(
2842
+ VisualJson,
2843
+ {
2844
+ value: valueRef.current,
2845
+ onChange: readOnly ? void 0 : handleChange,
2846
+ schema,
2847
+ children: /* @__PURE__ */ jsx8(
2848
+ EditorLayout,
2849
+ {
2850
+ treeShowValues,
2851
+ treeShowCounts,
2852
+ editorShowDescriptions,
2853
+ editorShowCounts,
2854
+ sidebarOpen
2855
+ }
2856
+ )
2857
+ },
2858
+ editorKey
2859
+ )
2860
+ ] });
2299
2861
  }
2300
2862
  function EditorLayout({
2301
2863
  treeShowValues,
@@ -2304,14 +2866,14 @@ function EditorLayout({
2304
2866
  editorShowCounts,
2305
2867
  sidebarOpen
2306
2868
  }) {
2307
- const [sidebarWidth, setSidebarWidth] = useState7(280);
2308
- const [isNarrow, setIsNarrow] = useState7(false);
2309
- const [activePanel, setActivePanel] = useState7("tree");
2310
- const containerRef = useRef8(null);
2311
- const dragging = useRef8(false);
2312
- const startX = useRef8(0);
2313
- const startWidth = useRef8(0);
2314
- useEffect7(() => {
2869
+ const [sidebarWidth, setSidebarWidth] = useState8(280);
2870
+ const [isNarrow, setIsNarrow] = useState8(false);
2871
+ const [activePanel, setActivePanel] = useState8("tree");
2872
+ const containerRef = useRef9(null);
2873
+ const dragging = useRef9(false);
2874
+ const startX = useRef9(0);
2875
+ const startWidth = useRef9(0);
2876
+ useEffect8(() => {
2315
2877
  function checkWidth() {
2316
2878
  if (containerRef.current) {
2317
2879
  setIsNarrow(containerRef.current.offsetWidth < 500);
@@ -2322,7 +2884,7 @@ function EditorLayout({
2322
2884
  if (containerRef.current) observer.observe(containerRef.current);
2323
2885
  return () => observer.disconnect();
2324
2886
  }, []);
2325
- const handleMouseDown = useCallback7(
2887
+ const handleMouseDown = useCallback8(
2326
2888
  (e) => {
2327
2889
  dragging.current = true;
2328
2890
  startX.current = e.clientX;
@@ -2352,7 +2914,7 @@ function EditorLayout({
2352
2914
  );
2353
2915
  if (isNarrow) {
2354
2916
  if (!sidebarOpen) {
2355
- return /* @__PURE__ */ jsx7(
2917
+ return /* @__PURE__ */ jsx8(
2356
2918
  "div",
2357
2919
  {
2358
2920
  ref: containerRef,
@@ -2362,7 +2924,7 @@ function EditorLayout({
2362
2924
  flex: 1,
2363
2925
  minHeight: 0
2364
2926
  },
2365
- children: /* @__PURE__ */ jsx7("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx7(
2927
+ children: /* @__PURE__ */ jsx8("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx8(
2366
2928
  FormView,
2367
2929
  {
2368
2930
  showDescriptions: editorShowDescriptions,
@@ -2372,7 +2934,7 @@ function EditorLayout({
2372
2934
  }
2373
2935
  );
2374
2936
  }
2375
- return /* @__PURE__ */ jsxs5(
2937
+ return /* @__PURE__ */ jsxs6(
2376
2938
  "div",
2377
2939
  {
2378
2940
  ref: containerRef,
@@ -2383,7 +2945,7 @@ function EditorLayout({
2383
2945
  minHeight: 0
2384
2946
  },
2385
2947
  children: [
2386
- /* @__PURE__ */ jsxs5(
2948
+ /* @__PURE__ */ jsxs6(
2387
2949
  "div",
2388
2950
  {
2389
2951
  style: {
@@ -2393,7 +2955,7 @@ function EditorLayout({
2393
2955
  backgroundColor: "var(--vj-bg-panel, #252526)"
2394
2956
  },
2395
2957
  children: [
2396
- /* @__PURE__ */ jsx7(
2958
+ /* @__PURE__ */ jsx8(
2397
2959
  "button",
2398
2960
  {
2399
2961
  onClick: () => setActivePanel("tree"),
@@ -2410,7 +2972,7 @@ function EditorLayout({
2410
2972
  children: "Tree"
2411
2973
  }
2412
2974
  ),
2413
- /* @__PURE__ */ jsx7(
2975
+ /* @__PURE__ */ jsx8(
2414
2976
  "button",
2415
2977
  {
2416
2978
  onClick: () => setActivePanel("form"),
@@ -2430,7 +2992,7 @@ function EditorLayout({
2430
2992
  ]
2431
2993
  }
2432
2994
  ),
2433
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, minHeight: 0, overflow: "hidden" }, children: activePanel === "tree" ? /* @__PURE__ */ jsxs5(
2995
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, minHeight: 0, overflow: "hidden" }, children: activePanel === "tree" ? /* @__PURE__ */ jsxs6(
2434
2996
  "div",
2435
2997
  {
2436
2998
  style: {
@@ -2439,8 +3001,8 @@ function EditorLayout({
2439
3001
  height: "100%"
2440
3002
  },
2441
3003
  children: [
2442
- /* @__PURE__ */ jsx7(SearchBar, {}),
2443
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ jsx7(
3004
+ /* @__PURE__ */ jsx8(SearchBar, {}),
3005
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ jsx8(
2444
3006
  TreeView,
2445
3007
  {
2446
3008
  showValues: treeShowValues,
@@ -2449,7 +3011,7 @@ function EditorLayout({
2449
3011
  ) })
2450
3012
  ]
2451
3013
  }
2452
- ) : /* @__PURE__ */ jsx7(
3014
+ ) : /* @__PURE__ */ jsx8(
2453
3015
  "div",
2454
3016
  {
2455
3017
  style: {
@@ -2457,7 +3019,7 @@ function EditorLayout({
2457
3019
  flexDirection: "column",
2458
3020
  height: "100%"
2459
3021
  },
2460
- children: /* @__PURE__ */ jsx7("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx7(
3022
+ children: /* @__PURE__ */ jsx8("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx8(
2461
3023
  FormView,
2462
3024
  {
2463
3025
  showDescriptions: editorShowDescriptions,
@@ -2470,8 +3032,8 @@ function EditorLayout({
2470
3032
  }
2471
3033
  );
2472
3034
  }
2473
- return /* @__PURE__ */ jsxs5("div", { ref: containerRef, style: { display: "flex", flex: 1, minHeight: 0 }, children: [
2474
- /* @__PURE__ */ jsxs5(
3035
+ return /* @__PURE__ */ jsxs6("div", { ref: containerRef, style: { display: "flex", flex: 1, minHeight: 0 }, children: [
3036
+ /* @__PURE__ */ jsxs6(
2475
3037
  "div",
2476
3038
  {
2477
3039
  style: {
@@ -2483,12 +3045,12 @@ function EditorLayout({
2483
3045
  transition: "width 0.2s ease"
2484
3046
  },
2485
3047
  children: [
2486
- /* @__PURE__ */ jsx7(SearchBar, {}),
2487
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ jsx7(TreeView, { showValues: treeShowValues, showCounts: treeShowCounts }) })
3048
+ /* @__PURE__ */ jsx8(SearchBar, {}),
3049
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ jsx8(TreeView, { showValues: treeShowValues, showCounts: treeShowCounts }) })
2488
3050
  ]
2489
3051
  }
2490
3052
  ),
2491
- sidebarOpen && /* @__PURE__ */ jsx7(
3053
+ sidebarOpen && /* @__PURE__ */ jsx8(
2492
3054
  "div",
2493
3055
  {
2494
3056
  style: {
@@ -2498,7 +3060,7 @@ function EditorLayout({
2498
3060
  position: "relative",
2499
3061
  transition: "background-color 0.15s"
2500
3062
  },
2501
- children: /* @__PURE__ */ jsx7(
3063
+ children: /* @__PURE__ */ jsx8(
2502
3064
  "div",
2503
3065
  {
2504
3066
  onMouseDown: handleMouseDown,
@@ -2527,7 +3089,7 @@ function EditorLayout({
2527
3089
  )
2528
3090
  }
2529
3091
  ),
2530
- /* @__PURE__ */ jsx7(
3092
+ /* @__PURE__ */ jsx8(
2531
3093
  "div",
2532
3094
  {
2533
3095
  style: {
@@ -2537,7 +3099,7 @@ function EditorLayout({
2537
3099
  minWidth: 0,
2538
3100
  overflow: "hidden"
2539
3101
  },
2540
- children: /* @__PURE__ */ jsx7("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx7(
3102
+ children: /* @__PURE__ */ jsx8("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx8(
2541
3103
  FormView,
2542
3104
  {
2543
3105
  showDescriptions: editorShowDescriptions,
@@ -2549,436 +3111,8 @@ function EditorLayout({
2549
3111
  ] });
2550
3112
  }
2551
3113
 
2552
- // src/property-editor.tsx
2553
- import { useState as useState8, useCallback as useCallback8 } from "react";
2554
- import {
2555
- setValue as setValue2,
2556
- setKey as setKey2,
2557
- addProperty as addProperty2,
2558
- removeNode as removeNode3,
2559
- changeType as changeType2,
2560
- duplicateNode as duplicateNode2,
2561
- toJson as toJson3,
2562
- getPropertySchema as getPropertySchema3,
2563
- resolveRef as resolveRef2
2564
- } from "@visual-json/core";
2565
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2566
- var ALL_TYPES = [
2567
- "string",
2568
- "number",
2569
- "boolean",
2570
- "null",
2571
- "object",
2572
- "array"
2573
- ];
2574
- function PropertyRow({ node, schemaProperty }) {
2575
- const { state, actions } = useStudio();
2576
- const isContainer = node.type === "object" || node.type === "array";
2577
- const [hoveredRow, setHoveredRow] = useState8(false);
2578
- function handleValueChange(newValue) {
2579
- let parsed;
2580
- if (newValue === "null") parsed = null;
2581
- else if (newValue === "true") parsed = true;
2582
- else if (newValue === "false") parsed = false;
2583
- else if ((node.type === "number" || schemaProperty?.type === "number" || schemaProperty?.type === "integer") && !isNaN(Number(newValue)) && newValue.trim() !== "")
2584
- parsed = Number(newValue);
2585
- else parsed = newValue;
2586
- const newTree = setValue2(state.tree, node.id, parsed);
2587
- actions.setTree(newTree);
2588
- }
2589
- function handleKeyChange(newKey) {
2590
- const newTree = setKey2(state.tree, node.id, newKey);
2591
- actions.setTree(newTree);
2592
- }
2593
- function handleRemove() {
2594
- const newTree = removeNode3(state.tree, node.id);
2595
- actions.setTree(newTree);
2596
- }
2597
- function handleAddChild() {
2598
- const key = node.type === "array" ? String(node.children.length) : `key${node.children.length}`;
2599
- const newTree = addProperty2(state.tree, node.id, key, "");
2600
- actions.setTree(newTree);
2601
- }
2602
- function displayValue() {
2603
- if (isContainer) {
2604
- return node.type === "array" ? `[${node.children.length} items]` : `{${node.children.length} keys}`;
2605
- }
2606
- if (node.value === null) return "";
2607
- if (node.value === void 0) return "";
2608
- if (typeof node.value === "string" && node.value === "") return "";
2609
- return String(node.value);
2610
- }
2611
- const hasEnumValues = schemaProperty?.enum && schemaProperty.enum.length > 0;
2612
- const description = schemaProperty?.description;
2613
- return /* @__PURE__ */ jsxs6(
2614
- "div",
2615
- {
2616
- onMouseEnter: () => setHoveredRow(true),
2617
- onMouseLeave: () => setHoveredRow(false),
2618
- children: [
2619
- /* @__PURE__ */ jsxs6(
2620
- "div",
2621
- {
2622
- style: {
2623
- display: "flex",
2624
- alignItems: "center",
2625
- gap: 8,
2626
- padding: "4px 12px",
2627
- borderBottom: "1px solid var(--vj-border-subtle, #2a2a2a)",
2628
- minHeight: 32,
2629
- backgroundColor: hoveredRow ? "var(--vj-bg-hover, #2a2d2e)" : "transparent"
2630
- },
2631
- children: [
2632
- /* @__PURE__ */ jsx8(
2633
- "input",
2634
- {
2635
- value: node.key,
2636
- onChange: (e) => handleKeyChange(e.target.value),
2637
- style: {
2638
- background: "none",
2639
- border: "1px solid transparent",
2640
- borderRadius: 3,
2641
- color: "var(--vj-text, #cccccc)",
2642
- fontFamily: "var(--vj-font, monospace)",
2643
- fontSize: 13,
2644
- padding: "2px 6px",
2645
- width: 140,
2646
- flexShrink: 0
2647
- }
2648
- }
2649
- ),
2650
- !isContainer ? hasEnumValues ? /* @__PURE__ */ jsx8(
2651
- "select",
2652
- {
2653
- value: displayValue(),
2654
- onChange: (e) => handleValueChange(e.target.value),
2655
- style: {
2656
- background: "var(--vj-input-bg, #3c3c3c)",
2657
- border: "1px solid var(--vj-input-border, #555555)",
2658
- borderRadius: 3,
2659
- color: node.type === "string" ? "var(--vj-string, #ce9178)" : "var(--vj-number, #b5cea8)",
2660
- fontFamily: "var(--vj-font, monospace)",
2661
- fontSize: 13,
2662
- padding: "2px 6px",
2663
- flex: 1,
2664
- cursor: "pointer"
2665
- },
2666
- children: schemaProperty.enum.map((v, i) => /* @__PURE__ */ jsx8("option", { value: String(v), children: String(v) }, i))
2667
- }
2668
- ) : /* @__PURE__ */ jsx8(
2669
- "input",
2670
- {
2671
- value: displayValue(),
2672
- onChange: (e) => handleValueChange(e.target.value),
2673
- placeholder: "<value>",
2674
- style: {
2675
- background: "none",
2676
- border: "1px solid transparent",
2677
- borderRadius: 3,
2678
- color: node.type === "string" ? "var(--vj-string, #ce9178)" : "var(--vj-number, #b5cea8)",
2679
- fontFamily: "var(--vj-font, monospace)",
2680
- fontSize: 13,
2681
- padding: "2px 6px",
2682
- flex: 1,
2683
- textAlign: "right"
2684
- }
2685
- }
2686
- ) : /* @__PURE__ */ jsx8(
2687
- "span",
2688
- {
2689
- style: {
2690
- color: "var(--vj-text-dim, #666666)",
2691
- fontFamily: "var(--vj-font, monospace)",
2692
- fontSize: 13,
2693
- flex: 1,
2694
- textAlign: "right"
2695
- },
2696
- children: displayValue()
2697
- }
2698
- ),
2699
- /* @__PURE__ */ jsxs6(
2700
- "div",
2701
- {
2702
- style: {
2703
- display: "flex",
2704
- gap: 2,
2705
- opacity: hoveredRow ? 1 : 0,
2706
- transition: "opacity 0.1s",
2707
- flexShrink: 0
2708
- },
2709
- children: [
2710
- isContainer && /* @__PURE__ */ jsx8(
2711
- "button",
2712
- {
2713
- onClick: handleAddChild,
2714
- style: {
2715
- background: "none",
2716
- border: "none",
2717
- color: "var(--vj-text-muted, #888888)",
2718
- cursor: "pointer",
2719
- padding: "2px 4px",
2720
- fontSize: 15,
2721
- fontFamily: "var(--vj-font, monospace)",
2722
- borderRadius: 3,
2723
- lineHeight: 1
2724
- },
2725
- title: "Add child",
2726
- children: "+"
2727
- }
2728
- ),
2729
- /* @__PURE__ */ jsx8(
2730
- "button",
2731
- {
2732
- onClick: handleRemove,
2733
- style: {
2734
- background: "none",
2735
- border: "none",
2736
- color: "var(--vj-text-muted, #888888)",
2737
- cursor: "pointer",
2738
- padding: "2px 4px",
2739
- fontSize: 15,
2740
- fontFamily: "var(--vj-font, monospace)",
2741
- borderRadius: 3,
2742
- lineHeight: 1
2743
- },
2744
- title: "Remove",
2745
- children: "\xD7"
2746
- }
2747
- )
2748
- ]
2749
- }
2750
- )
2751
- ]
2752
- }
2753
- ),
2754
- description && /* @__PURE__ */ jsx8(
2755
- "div",
2756
- {
2757
- style: {
2758
- padding: "2px 12px 4px 44px",
2759
- fontSize: 11,
2760
- color: "var(--vj-text-dim, #666666)",
2761
- fontFamily: "var(--vj-font, monospace)",
2762
- borderBottom: "1px solid var(--vj-border-subtle, #2a2a2a)"
2763
- },
2764
- children: description
2765
- }
2766
- )
2767
- ]
2768
- }
2769
- );
2770
- }
2771
- function PropertyEditor({ className }) {
2772
- const { state, actions } = useStudio();
2773
- const selectedNode = state.selectedNodeId ? state.tree.nodesById.get(state.selectedNodeId) : null;
2774
- const handleChangeType = useCallback8(
2775
- (newType) => {
2776
- if (!selectedNode) return;
2777
- const newTree = changeType2(state.tree, selectedNode.id, newType);
2778
- actions.setTree(newTree);
2779
- },
2780
- [state.tree, selectedNode, actions]
2781
- );
2782
- const handleDuplicate = useCallback8(() => {
2783
- if (!selectedNode) return;
2784
- const newTree = duplicateNode2(state.tree, selectedNode.id);
2785
- actions.setTree(newTree);
2786
- }, [state.tree, selectedNode, actions]);
2787
- const handleCopyPath = useCallback8(() => {
2788
- if (!selectedNode) return;
2789
- navigator.clipboard.writeText(selectedNode.path).catch(() => {
2790
- });
2791
- }, [selectedNode]);
2792
- const handleCopyValue = useCallback8(() => {
2793
- if (!selectedNode) return;
2794
- const value = toJson3(selectedNode);
2795
- const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
2796
- navigator.clipboard.writeText(text).catch(() => {
2797
- });
2798
- }, [selectedNode]);
2799
- if (!selectedNode) {
2800
- return /* @__PURE__ */ jsx8(
2801
- "div",
2802
- {
2803
- className,
2804
- style: {
2805
- display: "flex",
2806
- alignItems: "center",
2807
- justifyContent: "center",
2808
- backgroundColor: "var(--vj-bg, #1e1e1e)",
2809
- color: "var(--vj-text-dimmer, #555555)",
2810
- fontFamily: "var(--vj-font, monospace)",
2811
- fontSize: 13,
2812
- height: "100%"
2813
- },
2814
- children: "Select a node to edit"
2815
- }
2816
- );
2817
- }
2818
- const isContainer = selectedNode.type === "object" || selectedNode.type === "array";
2819
- const schema = state.schema ?? void 0;
2820
- const schemaTitle = schema?.title;
2821
- function getChildSchema(childKey) {
2822
- if (!schema || !selectedNode) return void 0;
2823
- const childPath = selectedNode.path === "/" ? `/${childKey}` : `${selectedNode.path}/${childKey}`;
2824
- const raw = getPropertySchema3(schema, childPath);
2825
- return raw ? resolveRef2(raw, schema) : void 0;
2826
- }
2827
- function handleAdd() {
2828
- if (!selectedNode) return;
2829
- const key = selectedNode.type === "array" ? String(selectedNode.children.length) : `key${selectedNode.children.length}`;
2830
- const newTree = addProperty2(state.tree, selectedNode.id, key, "");
2831
- actions.setTree(newTree);
2832
- }
2833
- return /* @__PURE__ */ jsxs6(
2834
- "div",
2835
- {
2836
- className,
2837
- style: {
2838
- backgroundColor: "var(--vj-bg, #1e1e1e)",
2839
- color: "var(--vj-text, #cccccc)",
2840
- overflow: "auto",
2841
- height: "100%",
2842
- display: "flex",
2843
- flexDirection: "column"
2844
- },
2845
- children: [
2846
- /* @__PURE__ */ jsxs6(
2847
- "div",
2848
- {
2849
- style: {
2850
- display: "flex",
2851
- alignItems: "center",
2852
- justifyContent: "space-between",
2853
- padding: "6px 12px",
2854
- borderBottom: "1px solid var(--vj-border, #333333)",
2855
- fontSize: 12,
2856
- color: "var(--vj-text-muted, #999999)",
2857
- fontFamily: "var(--vj-font, monospace)",
2858
- flexShrink: 0,
2859
- backgroundColor: "var(--vj-bg-panel, #252526)"
2860
- },
2861
- children: [
2862
- /* @__PURE__ */ jsxs6(
2863
- "div",
2864
- {
2865
- style: {
2866
- display: "flex",
2867
- flexDirection: "column",
2868
- gap: 2,
2869
- flex: 1,
2870
- minWidth: 0
2871
- },
2872
- children: [
2873
- /* @__PURE__ */ jsx8(Breadcrumbs, {}),
2874
- schemaTitle && /* @__PURE__ */ jsx8(
2875
- "span",
2876
- {
2877
- style: { fontSize: 10, color: "var(--vj-text-dim, #666666)" },
2878
- children: schemaTitle
2879
- }
2880
- )
2881
- ]
2882
- }
2883
- ),
2884
- /* @__PURE__ */ jsxs6(
2885
- "div",
2886
- {
2887
- style: {
2888
- display: "flex",
2889
- alignItems: "center",
2890
- gap: 4,
2891
- flexShrink: 0
2892
- },
2893
- children: [
2894
- /* @__PURE__ */ jsx8(
2895
- "select",
2896
- {
2897
- value: selectedNode.type,
2898
- onChange: (e) => handleChangeType(e.target.value),
2899
- style: {
2900
- background: "var(--vj-input-bg, #3c3c3c)",
2901
- border: "1px solid var(--vj-input-border, #555555)",
2902
- borderRadius: 3,
2903
- color: "var(--vj-text, #cccccc)",
2904
- fontSize: 11,
2905
- fontFamily: "var(--vj-font, monospace)",
2906
- padding: "1px 4px",
2907
- cursor: "pointer"
2908
- },
2909
- title: "Change type",
2910
- children: ALL_TYPES.map((t) => /* @__PURE__ */ jsx8("option", { value: t, children: t }, t))
2911
- }
2912
- ),
2913
- /* @__PURE__ */ jsx8(
2914
- "button",
2915
- {
2916
- onClick: handleCopyPath,
2917
- style: actionButtonStyle,
2918
- title: "Copy path",
2919
- children: "path"
2920
- }
2921
- ),
2922
- /* @__PURE__ */ jsx8(
2923
- "button",
2924
- {
2925
- onClick: handleCopyValue,
2926
- style: actionButtonStyle,
2927
- title: "Copy value",
2928
- children: "value"
2929
- }
2930
- ),
2931
- selectedNode.parentId && /* @__PURE__ */ jsx8(
2932
- "button",
2933
- {
2934
- onClick: handleDuplicate,
2935
- style: actionButtonStyle,
2936
- title: "Duplicate",
2937
- children: "dup"
2938
- }
2939
- ),
2940
- isContainer && /* @__PURE__ */ jsx8(
2941
- "button",
2942
- {
2943
- onClick: handleAdd,
2944
- style: {
2945
- ...actionButtonStyle,
2946
- border: "1px solid var(--vj-input-border, #555555)"
2947
- },
2948
- children: "+ Add"
2949
- }
2950
- )
2951
- ]
2952
- }
2953
- )
2954
- ]
2955
- }
2956
- ),
2957
- /* @__PURE__ */ jsx8("div", { style: { flex: 1, overflow: "auto" }, children: isContainer ? selectedNode.children.map((child) => /* @__PURE__ */ jsx8(
2958
- PropertyRow,
2959
- {
2960
- node: child,
2961
- schemaProperty: getChildSchema(child.key)
2962
- },
2963
- child.id
2964
- )) : /* @__PURE__ */ jsx8(PropertyRow, { node: selectedNode }) })
2965
- ]
2966
- }
2967
- );
2968
- }
2969
- var actionButtonStyle = {
2970
- background: "none",
2971
- border: "none",
2972
- borderRadius: 3,
2973
- color: "var(--vj-text-muted, #888888)",
2974
- cursor: "pointer",
2975
- padding: "1px 6px",
2976
- fontSize: 11,
2977
- fontFamily: "var(--vj-font, monospace)"
2978
- };
2979
-
2980
3114
  // src/diff-view.tsx
2981
- import { useMemo as useMemo5 } from "react";
3115
+ import { useMemo as useMemo7 } from "react";
2982
3116
  import { computeDiff } from "@visual-json/core";
2983
3117
  import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2984
3118
  var DIFF_COLORS = {
@@ -3057,7 +3191,7 @@ function DiffView({
3057
3191
  currentJson,
3058
3192
  className
3059
3193
  }) {
3060
- const entries = useMemo5(
3194
+ const entries = useMemo7(
3061
3195
  () => computeDiff(originalJson, currentJson),
3062
3196
  [originalJson, currentJson]
3063
3197
  );
@@ -3136,7 +3270,6 @@ export {
3136
3270
  DiffView,
3137
3271
  FormView,
3138
3272
  JsonEditor,
3139
- PropertyEditor,
3140
3273
  SearchBar,
3141
3274
  StudioContext,
3142
3275
  TreeView,