@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.js CHANGED
@@ -25,7 +25,6 @@ __export(index_exports, {
25
25
  DiffView: () => DiffView,
26
26
  FormView: () => FormView,
27
27
  JsonEditor: () => JsonEditor,
28
- PropertyEditor: () => PropertyEditor,
29
28
  SearchBar: () => SearchBar,
30
29
  StudioContext: () => StudioContext,
31
30
  TreeView: () => TreeView,
@@ -36,11 +35,11 @@ __export(index_exports, {
36
35
  module.exports = __toCommonJS(index_exports);
37
36
 
38
37
  // src/json-editor.tsx
39
- var import_react9 = require("react");
38
+ var import_react10 = require("react");
40
39
 
41
40
  // src/visual-json.tsx
42
41
  var import_react2 = require("react");
43
- var import_core = require("@visual-json/core");
42
+ var import_core2 = require("@visual-json/core");
44
43
 
45
44
  // src/context.ts
46
45
  var import_react = require("react");
@@ -53,6 +52,91 @@ function useStudio() {
53
52
  return ctx;
54
53
  }
55
54
 
55
+ // src/get-visible-nodes.ts
56
+ function getVisibleNodes(root, isExpanded) {
57
+ const result = [];
58
+ function walk(node) {
59
+ result.push(node);
60
+ if (isExpanded(node.id) && (node.type === "object" || node.type === "array")) {
61
+ for (const child of node.children) {
62
+ walk(child);
63
+ }
64
+ }
65
+ }
66
+ walk(root);
67
+ return result;
68
+ }
69
+
70
+ // src/selection-utils.ts
71
+ var import_core = require("@visual-json/core");
72
+ function computeSelectAllIds(tree, focusedNodeId, currentlySelected) {
73
+ if (!focusedNodeId) return null;
74
+ let node = tree.nodesById.get(focusedNodeId);
75
+ if (!node) return null;
76
+ while (node) {
77
+ const parent = node.parentId ? tree.nodesById.get(node.parentId) : void 0;
78
+ const siblings = parent ? parent.children : [tree.root];
79
+ const siblingIds = new Set(siblings.map((s) => s.id));
80
+ const allSelected = siblings.every((s) => currentlySelected.has(s.id));
81
+ if (!allSelected) {
82
+ return siblingIds;
83
+ }
84
+ if (!parent || !parent.parentId) return siblingIds;
85
+ node = parent;
86
+ }
87
+ return null;
88
+ }
89
+ function computeRangeIds(visibleNodes, anchorId, targetId) {
90
+ const anchorIdx = visibleNodes.findIndex((n) => n.id === anchorId);
91
+ const targetIdx = visibleNodes.findIndex((n) => n.id === targetId);
92
+ if (anchorIdx === -1 || targetIdx === -1) return null;
93
+ const start = Math.min(anchorIdx, targetIdx);
94
+ const end = Math.max(anchorIdx, targetIdx);
95
+ const ids = /* @__PURE__ */ new Set();
96
+ for (let i = start; i <= end; i++) {
97
+ ids.add(visibleNodes[i].id);
98
+ }
99
+ return ids;
100
+ }
101
+ function deleteSelectedNodes(tree, selectedIds, visibleNodes) {
102
+ const idsToDelete = [...selectedIds].filter((id) => {
103
+ const node = tree.nodesById.get(id);
104
+ if (!node || node.parentId === null) return false;
105
+ let cur = tree.nodesById.get(node.parentId);
106
+ while (cur) {
107
+ if (selectedIds.has(cur.id)) return false;
108
+ cur = cur.parentId ? tree.nodesById.get(cur.parentId) : void 0;
109
+ }
110
+ return true;
111
+ });
112
+ if (idsToDelete.length === 0) return { newTree: tree, nextFocusId: null };
113
+ const firstDeletedIdx = visibleNodes.findIndex((n) => selectedIds.has(n.id));
114
+ let newTree = tree;
115
+ for (const id of idsToDelete) {
116
+ if (newTree.nodesById.has(id)) {
117
+ newTree = (0, import_core.removeNode)(newTree, id);
118
+ }
119
+ }
120
+ let nextFocusId = null;
121
+ for (let i = firstDeletedIdx; i < visibleNodes.length; i++) {
122
+ const id = visibleNodes[i].id;
123
+ if (!selectedIds.has(id) && newTree.nodesById.has(id)) {
124
+ nextFocusId = id;
125
+ break;
126
+ }
127
+ }
128
+ if (!nextFocusId) {
129
+ for (let i = firstDeletedIdx - 1; i >= 0; i--) {
130
+ const id = visibleNodes[i].id;
131
+ if (!selectedIds.has(id) && newTree.nodesById.has(id)) {
132
+ nextFocusId = id;
133
+ break;
134
+ }
135
+ }
136
+ }
137
+ return { newTree, nextFocusId };
138
+ }
139
+
56
140
  // src/visual-json.tsx
57
141
  var import_jsx_runtime = require("react/jsx-runtime");
58
142
  function collectAllIds(node) {
@@ -68,12 +152,44 @@ function VisualJson({
68
152
  schema,
69
153
  children
70
154
  }) {
71
- const [tree, setTreeState] = (0, import_react2.useState)(() => (0, import_core.fromJson)(value));
72
- const [selectedNodeId, setSelectedNodeId] = (0, import_react2.useState)(null);
155
+ const [tree, setTreeState] = (0, import_react2.useState)(() => (0, import_core2.fromJson)(value));
156
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react2.useState)(null);
157
+ const [selectedNodeIds, setSelectedNodeIdsState] = (0, import_react2.useState)(
158
+ () => /* @__PURE__ */ new Set()
159
+ );
160
+ const selectedNodeIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
161
+ const setSelectedNodeIds = (0, import_react2.useCallback)((ids) => {
162
+ selectedNodeIdsRef.current = ids;
163
+ setSelectedNodeIdsState(ids);
164
+ }, []);
165
+ const anchorNodeIdRef = (0, import_react2.useRef)(null);
166
+ const [anchorNodeId, setAnchorNodeIdState] = (0, import_react2.useState)(null);
167
+ const [drillDownNodeId, setDrillDownNodeId] = (0, import_react2.useState)(null);
73
168
  const [expandedNodeIds, setExpandedNodeIds] = (0, import_react2.useState)(
74
169
  () => /* @__PURE__ */ new Set([tree.root.id])
75
170
  );
76
- const historyRef = (0, import_react2.useRef)(new import_core.History());
171
+ const setAnchorNodeId = (0, import_react2.useCallback)((id) => {
172
+ anchorNodeIdRef.current = id;
173
+ setAnchorNodeIdState(id);
174
+ }, []);
175
+ const focusSelectAndDrillDown = (0, import_react2.useCallback)(
176
+ (nodeId) => {
177
+ setFocusedNodeId(nodeId);
178
+ setSelectedNodeIds(nodeId ? /* @__PURE__ */ new Set([nodeId]) : /* @__PURE__ */ new Set());
179
+ setAnchorNodeId(nodeId);
180
+ setDrillDownNodeId(nodeId);
181
+ },
182
+ [setSelectedNodeIds, setAnchorNodeId]
183
+ );
184
+ const visibleNodes = (0, import_react2.useMemo)(
185
+ () => getVisibleNodes(tree.root, (id) => expandedNodeIds.has(id)),
186
+ [tree.root, expandedNodeIds]
187
+ );
188
+ const visibleNodesOverrideRef = (0, import_react2.useRef)(null);
189
+ const setVisibleNodesOverride = (0, import_react2.useCallback)((nodes) => {
190
+ visibleNodesOverrideRef.current = nodes;
191
+ }, []);
192
+ const historyRef = (0, import_react2.useRef)(new import_core2.History());
77
193
  const isInternalChange = (0, import_react2.useRef)(false);
78
194
  const hasMounted = (0, import_react2.useRef)(false);
79
195
  const [canUndo, setCanUndo] = (0, import_react2.useState)(false);
@@ -98,11 +214,11 @@ function VisualJson({
98
214
  isInternalChange.current = false;
99
215
  return;
100
216
  }
101
- const newTree = (0, import_core.fromJson)(value);
217
+ const newTree = (0, import_core2.fromJson)(value);
102
218
  setTreeState(newTree);
103
219
  setExpandedNodeIds(/* @__PURE__ */ new Set([newTree.root.id]));
104
- setSelectedNodeId(null);
105
- historyRef.current = new import_core.History();
220
+ focusSelectAndDrillDown(null);
221
+ historyRef.current = new import_core2.History();
106
222
  historyRef.current.push(newTree);
107
223
  setCanUndo(false);
108
224
  setCanRedo(false);
@@ -118,7 +234,7 @@ function VisualJson({
118
234
  setCanUndo(historyRef.current.canUndo);
119
235
  setCanRedo(historyRef.current.canRedo);
120
236
  isInternalChange.current = true;
121
- onChange?.((0, import_core.toJson)(newTree.root));
237
+ onChange?.((0, import_core2.toJson)(newTree.root));
122
238
  },
123
239
  [onChange]
124
240
  );
@@ -129,7 +245,7 @@ function VisualJson({
129
245
  setCanUndo(historyRef.current.canUndo);
130
246
  setCanRedo(historyRef.current.canRedo);
131
247
  isInternalChange.current = true;
132
- onChange?.((0, import_core.toJson)(prev.root));
248
+ onChange?.((0, import_core2.toJson)(prev.root));
133
249
  }
134
250
  }, [onChange]);
135
251
  const redo = (0, import_react2.useCallback)(() => {
@@ -139,7 +255,7 @@ function VisualJson({
139
255
  setCanUndo(historyRef.current.canUndo);
140
256
  setCanRedo(historyRef.current.canRedo);
141
257
  isInternalChange.current = true;
142
- onChange?.((0, import_core.toJson)(next.root));
258
+ onChange?.((0, import_core2.toJson)(next.root));
143
259
  }
144
260
  }, [onChange]);
145
261
  (0, import_react2.useEffect)(() => {
@@ -159,8 +275,66 @@ function VisualJson({
159
275
  document.addEventListener("keydown", handleKeyDown);
160
276
  return () => document.removeEventListener("keydown", handleKeyDown);
161
277
  }, [undo, redo]);
162
- const selectNode = (0, import_react2.useCallback)((nodeId) => {
163
- setSelectedNodeId(nodeId);
278
+ const selectNode = (0, import_react2.useCallback)(
279
+ (nodeId) => {
280
+ setFocusedNodeId(nodeId);
281
+ setSelectedNodeIds(nodeId ? /* @__PURE__ */ new Set([nodeId]) : /* @__PURE__ */ new Set());
282
+ setAnchorNodeId(nodeId);
283
+ },
284
+ [setSelectedNodeIds, setAnchorNodeId]
285
+ );
286
+ const selectAndDrillDown = focusSelectAndDrillDown;
287
+ const toggleNodeSelection = (0, import_react2.useCallback)(
288
+ (nodeId) => {
289
+ const next = new Set(selectedNodeIdsRef.current);
290
+ if (next.has(nodeId)) {
291
+ next.delete(nodeId);
292
+ } else {
293
+ next.add(nodeId);
294
+ }
295
+ setSelectedNodeIds(next);
296
+ if (next.size === 0) {
297
+ setFocusedNodeId(null);
298
+ setAnchorNodeId(null);
299
+ } else {
300
+ setFocusedNodeId(nodeId);
301
+ setAnchorNodeId(nodeId);
302
+ }
303
+ },
304
+ [setSelectedNodeIds, setAnchorNodeId]
305
+ );
306
+ const selectNodeRange = (0, import_react2.useCallback)(
307
+ (toNodeId) => {
308
+ const nodes = visibleNodesOverrideRef.current ?? visibleNodes;
309
+ const anchor = anchorNodeIdRef.current;
310
+ if (!anchor) {
311
+ setFocusedNodeId(toNodeId);
312
+ setSelectedNodeIds(/* @__PURE__ */ new Set([toNodeId]));
313
+ setAnchorNodeId(toNodeId);
314
+ return;
315
+ }
316
+ const rangeIds = computeRangeIds(nodes, anchor, toNodeId);
317
+ if (!rangeIds) {
318
+ setFocusedNodeId(toNodeId);
319
+ setSelectedNodeIds(/* @__PURE__ */ new Set([toNodeId]));
320
+ setAnchorNodeId(toNodeId);
321
+ return;
322
+ }
323
+ setSelectedNodeIds(rangeIds);
324
+ setFocusedNodeId(toNodeId);
325
+ },
326
+ [visibleNodes, setSelectedNodeIds, setAnchorNodeId]
327
+ );
328
+ const setSelection = (0, import_react2.useCallback)(
329
+ (focusedId, newSelectedIds, newAnchorId) => {
330
+ setFocusedNodeId(focusedId);
331
+ setSelectedNodeIds(newSelectedIds);
332
+ setAnchorNodeId(newAnchorId);
333
+ },
334
+ [setSelectedNodeIds, setAnchorNodeId]
335
+ );
336
+ const drillDown = (0, import_react2.useCallback)((nodeId) => {
337
+ setDrillDownNodeId(nodeId);
164
338
  }, []);
165
339
  const toggleExpand = (0, import_react2.useCallback)((nodeId) => {
166
340
  setExpandedNodeIds((prev) => {
@@ -200,13 +374,14 @@ function VisualJson({
200
374
  setSearchMatchNodeIds(/* @__PURE__ */ new Set());
201
375
  return;
202
376
  }
203
- const matches = (0, import_core.searchNodes)(tree, query);
377
+ const matches = (0, import_core2.searchNodes)(tree, query);
204
378
  setSearchMatches(matches);
205
379
  setSearchMatchIndex(0);
206
380
  const matchIds = new Set(matches.map((m) => m.nodeId));
207
381
  setSearchMatchNodeIds(matchIds);
208
382
  if (matches.length > 0) {
209
- const ancestors = (0, import_core.getAncestorIds)(
383
+ const firstId = matches[0].nodeId;
384
+ const ancestors = (0, import_core2.getAncestorIds)(
210
385
  tree,
211
386
  matches.map((m) => m.nodeId)
212
387
  );
@@ -215,7 +390,7 @@ function VisualJson({
215
390
  for (const id of ancestors) next.add(id);
216
391
  return next;
217
392
  });
218
- setSelectedNodeId(matches[0].nodeId);
393
+ focusSelectAndDrillDown(firstId);
219
394
  }
220
395
  },
221
396
  [tree]
@@ -224,17 +399,17 @@ function VisualJson({
224
399
  if (searchMatches.length === 0) return;
225
400
  const nextIdx = (searchMatchIndex + 1) % searchMatches.length;
226
401
  setSearchMatchIndex(nextIdx);
227
- setSelectedNodeId(searchMatches[nextIdx].nodeId);
228
- }, [searchMatches, searchMatchIndex]);
402
+ focusSelectAndDrillDown(searchMatches[nextIdx].nodeId);
403
+ }, [searchMatches, searchMatchIndex, focusSelectAndDrillDown]);
229
404
  const prevSearchMatch = (0, import_react2.useCallback)(() => {
230
405
  if (searchMatches.length === 0) return;
231
406
  const prevIdx = (searchMatchIndex - 1 + searchMatches.length) % searchMatches.length;
232
407
  setSearchMatchIndex(prevIdx);
233
- setSelectedNodeId(searchMatches[prevIdx].nodeId);
234
- }, [searchMatches, searchMatchIndex]);
408
+ focusSelectAndDrillDown(searchMatches[prevIdx].nodeId);
409
+ }, [searchMatches, searchMatchIndex, focusSelectAndDrillDown]);
235
410
  (0, import_react2.useEffect)(() => {
236
411
  if (!searchQuery.trim()) return;
237
- const matches = (0, import_core.searchNodes)(tree, searchQuery);
412
+ const matches = (0, import_core2.searchNodes)(tree, searchQuery);
238
413
  setSearchMatches(matches);
239
414
  setSearchMatchIndex(
240
415
  (prev) => Math.min(prev, Math.max(matches.length - 1, 0))
@@ -244,7 +419,10 @@ function VisualJson({
244
419
  const state = (0, import_react2.useMemo)(
245
420
  () => ({
246
421
  tree,
247
- selectedNodeId,
422
+ focusedNodeId,
423
+ selectedNodeIds,
424
+ anchorNodeId,
425
+ drillDownNodeId,
248
426
  expandedNodeIds,
249
427
  schema: schema ?? null,
250
428
  searchQuery,
@@ -254,7 +432,10 @@ function VisualJson({
254
432
  }),
255
433
  [
256
434
  tree,
257
- selectedNodeId,
435
+ focusedNodeId,
436
+ selectedNodeIds,
437
+ anchorNodeId,
438
+ drillDownNodeId,
258
439
  expandedNodeIds,
259
440
  schema,
260
441
  searchQuery,
@@ -267,6 +448,12 @@ function VisualJson({
267
448
  () => ({
268
449
  setTree,
269
450
  selectNode,
451
+ selectAndDrillDown,
452
+ toggleNodeSelection,
453
+ selectNodeRange,
454
+ setSelection,
455
+ setVisibleNodesOverride,
456
+ drillDown,
270
457
  toggleExpand,
271
458
  expandNode,
272
459
  collapseNode,
@@ -283,6 +470,12 @@ function VisualJson({
283
470
  [
284
471
  setTree,
285
472
  selectNode,
473
+ selectAndDrillDown,
474
+ toggleNodeSelection,
475
+ selectNodeRange,
476
+ setSelection,
477
+ setVisibleNodesOverride,
478
+ drillDown,
286
479
  toggleExpand,
287
480
  expandNode,
288
481
  collapseNode,
@@ -303,7 +496,7 @@ function VisualJson({
303
496
 
304
497
  // src/tree-view.tsx
305
498
  var import_react5 = require("react");
306
- var import_core3 = require("@visual-json/core");
499
+ var import_core4 = require("@visual-json/core");
307
500
 
308
501
  // src/context-menu.tsx
309
502
  var import_react3 = require("react");
@@ -417,96 +610,194 @@ function getDisplayKey(node, state) {
417
610
  return node.key;
418
611
  }
419
612
 
420
- // src/get-visible-nodes.ts
421
- function getVisibleNodes(root, isExpanded) {
422
- const result = [];
423
- function walk(node) {
424
- result.push(node);
425
- if (isExpanded(node.id) && (node.type === "object" || node.type === "array")) {
426
- for (const child of node.children) {
427
- walk(child);
428
- }
429
- }
430
- }
431
- walk(root);
432
- return result;
433
- }
434
-
435
613
  // src/use-drag-drop.ts
436
614
  var import_react4 = require("react");
437
- var import_core2 = require("@visual-json/core");
615
+ var import_core3 = require("@visual-json/core");
616
+
617
+ // src/theme.ts
618
+ var DEFAULT_CSS_VARS = {
619
+ "--vj-bg": "#1e1e1e",
620
+ "--vj-bg-panel": "#252526",
621
+ "--vj-bg-hover": "#2a2d2e",
622
+ "--vj-bg-selected": "#2a5a1e",
623
+ "--vj-bg-selected-muted": "#2a2d2e",
624
+ "--vj-bg-match": "#3a3520",
625
+ "--vj-bg-match-active": "#51502b",
626
+ "--vj-border": "#333333",
627
+ "--vj-border-subtle": "#2a2a2a",
628
+ "--vj-text": "#cccccc",
629
+ "--vj-text-muted": "#888888",
630
+ "--vj-text-dim": "#666666",
631
+ "--vj-text-dimmer": "#555555",
632
+ "--vj-string": "#ce9178",
633
+ "--vj-number": "#b5cea8",
634
+ "--vj-boolean": "#569cd6",
635
+ "--vj-accent": "#007acc",
636
+ "--vj-accent-muted": "#094771",
637
+ "--vj-input-bg": "#3c3c3c",
638
+ "--vj-input-border": "#555555",
639
+ "--vj-error": "#f48771",
640
+ "--vj-font": "monospace",
641
+ "--vj-input-font-size": "13px"
642
+ };
643
+
644
+ // src/use-drag-drop.ts
645
+ var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
438
646
  var INITIAL_DRAG_STATE = {
439
- draggedNodeId: null,
647
+ draggedNodeIds: EMPTY_SET,
440
648
  dropTargetNodeId: null,
441
649
  dropPosition: null
442
650
  };
443
- function useDragDrop() {
651
+ function sortByTreeOrder(root, ids) {
652
+ const result = [];
653
+ function walk(node) {
654
+ if (ids.has(node.id)) result.push(node.id);
655
+ for (const child of node.children) walk(child);
656
+ }
657
+ walk(root);
658
+ return result;
659
+ }
660
+ function setMultiDragImage(e, count) {
661
+ const ghost = document.createElement("div");
662
+ ghost.textContent = `${count} selected`;
663
+ const root = document.querySelector("[data-form-container], [role='tree']");
664
+ const cs = root ? getComputedStyle(root) : null;
665
+ const bg = cs?.getPropertyValue("--vj-bg-selected").trim() || DEFAULT_CSS_VARS["--vj-bg-selected"];
666
+ const fg = cs?.getPropertyValue("--vj-text-selected").trim() || cs?.getPropertyValue("--vj-text").trim() || DEFAULT_CSS_VARS["--vj-text"];
667
+ const font = cs?.getPropertyValue("--vj-font").trim() || DEFAULT_CSS_VARS["--vj-font"];
668
+ ghost.style.cssText = [
669
+ "position:fixed",
670
+ "top:-1000px",
671
+ "left:-1000px",
672
+ "padding:4px 12px",
673
+ `background:${bg}`,
674
+ `color:${fg}`,
675
+ `font-family:${font}`,
676
+ "font-size:13px",
677
+ "border-radius:4px",
678
+ "white-space:nowrap",
679
+ "pointer-events:none"
680
+ ].join(";");
681
+ document.body.appendChild(ghost);
682
+ e.dataTransfer.setDragImage(ghost, 0, 14);
683
+ requestAnimationFrame(() => ghost.remove());
684
+ }
685
+ function useDragDrop(visibleNodes, selectedNodeIds) {
444
686
  const { state, actions } = useStudio();
445
687
  const [dragState, setDragState] = (0, import_react4.useState)(INITIAL_DRAG_STATE);
446
688
  const dragStateRef = (0, import_react4.useRef)(dragState);
447
689
  dragStateRef.current = dragState;
448
- const handleDragStart = (0, import_react4.useCallback)((nodeId) => {
449
- setDragState({
450
- draggedNodeId: nodeId,
451
- dropTargetNodeId: null,
452
- dropPosition: null
453
- });
454
- }, []);
455
- const handleDragOver = (0, import_react4.useCallback)(
690
+ const visibleNodeIndexMap = (0, import_react4.useMemo)(() => {
691
+ const map = /* @__PURE__ */ new Map();
692
+ visibleNodes.forEach((n, i) => map.set(n.id, i));
693
+ return map;
694
+ }, [visibleNodes]);
695
+ const handleDragStart = (0, import_react4.useCallback)(
696
+ (nodeId) => {
697
+ let ids;
698
+ if (selectedNodeIds.size > 0 && selectedNodeIds.has(nodeId)) {
699
+ ids = selectedNodeIds;
700
+ } else {
701
+ ids = /* @__PURE__ */ new Set([nodeId]);
702
+ }
703
+ setDragState({
704
+ draggedNodeIds: ids,
705
+ dropTargetNodeId: null,
706
+ dropPosition: null
707
+ });
708
+ },
709
+ [selectedNodeIds]
710
+ );
711
+ const rawDragOver = (0, import_react4.useCallback)(
456
712
  (nodeId, position) => {
713
+ const draggedIds = dragStateRef.current.draggedNodeIds;
714
+ for (const draggedId of draggedIds) {
715
+ if (nodeId === draggedId || (0, import_core3.isDescendant)(state.tree, nodeId, draggedId)) {
716
+ return;
717
+ }
718
+ }
457
719
  setDragState((prev) => ({
458
720
  ...prev,
459
721
  dropTargetNodeId: nodeId,
460
722
  dropPosition: position
461
723
  }));
462
724
  },
463
- []
725
+ [state.tree]
726
+ );
727
+ const handleDragOver = (0, import_react4.useCallback)(
728
+ (nodeId, position) => {
729
+ if (position === "before") {
730
+ const idx = visibleNodeIndexMap.get(nodeId);
731
+ if (idx !== void 0 && idx > 0) {
732
+ rawDragOver(visibleNodes[idx - 1].id, "after");
733
+ return;
734
+ }
735
+ }
736
+ rawDragOver(nodeId, position);
737
+ },
738
+ [visibleNodes, visibleNodeIndexMap, rawDragOver]
464
739
  );
465
740
  const handleDragEnd = (0, import_react4.useCallback)(() => {
466
741
  setDragState(INITIAL_DRAG_STATE);
467
742
  }, []);
468
743
  const handleDrop = (0, import_react4.useCallback)(() => {
469
- const { draggedNodeId, dropTargetNodeId, dropPosition } = dragStateRef.current;
470
- if (!draggedNodeId || !dropTargetNodeId || !dropPosition) return;
471
- const draggedNode = state.tree.nodesById.get(draggedNodeId);
744
+ const { draggedNodeIds, dropTargetNodeId, dropPosition } = dragStateRef.current;
745
+ if (draggedNodeIds.size === 0 || !dropTargetNodeId || !dropPosition) return;
472
746
  const targetNode = state.tree.nodesById.get(dropTargetNodeId);
473
- if (!draggedNode || !targetNode) return;
474
- if (draggedNode.parentId && draggedNode.parentId === targetNode.parentId) {
475
- const parent = state.tree.nodesById.get(draggedNode.parentId);
476
- if (parent) {
477
- const fromIndex = parent.children.findIndex(
478
- (c) => c.id === draggedNodeId
479
- );
480
- let toIndex = parent.children.findIndex(
481
- (c) => c.id === dropTargetNodeId
482
- );
483
- if (dropPosition === "after") toIndex++;
484
- if (fromIndex < toIndex) toIndex--;
485
- if (fromIndex !== toIndex && fromIndex >= 0 && toIndex >= 0) {
486
- const newTree = (0, import_core2.reorderChildren)(
487
- state.tree,
488
- parent.id,
489
- fromIndex,
490
- toIndex
491
- );
492
- actions.setTree(newTree);
747
+ if (!targetNode || !targetNode.parentId) return;
748
+ for (const id of draggedNodeIds) {
749
+ if ((0, import_core3.isDescendant)(state.tree, dropTargetNodeId, id)) return;
750
+ }
751
+ const targetParentId = targetNode.parentId;
752
+ const targetParent = state.tree.nodesById.get(targetParentId);
753
+ if (!targetParent) return;
754
+ const parentChildren = targetParent.children;
755
+ const orderedDragIds = parentChildren.filter((c) => draggedNodeIds.has(c.id)).map((c) => c.id);
756
+ const allSameParent = orderedDragIds.length === draggedNodeIds.size && [...draggedNodeIds].every((id) => {
757
+ const n = state.tree.nodesById.get(id);
758
+ return n?.parentId === targetParentId;
759
+ });
760
+ if (allSameParent) {
761
+ const newTree = (0, import_core3.reorderChildrenMulti)(
762
+ state.tree,
763
+ targetParentId,
764
+ orderedDragIds,
765
+ dropTargetNodeId,
766
+ dropPosition
767
+ );
768
+ actions.setTree(newTree);
769
+ } else {
770
+ const orderedIds = sortByTreeOrder(state.tree.root, draggedNodeIds);
771
+ const draggedNodes = orderedIds.map((id) => state.tree.nodesById.get(id)).filter((n) => !!n && n.parentId !== null).map((n) => structuredClone(n));
772
+ let newTree = state.tree;
773
+ for (const id of [...orderedIds].reverse()) {
774
+ if (newTree.nodesById.has(id)) {
775
+ newTree = (0, import_core3.removeNode)(newTree, id);
493
776
  }
494
777
  }
495
- } else if (targetNode.parentId) {
496
- const newParent = state.tree.nodesById.get(targetNode.parentId);
497
- if (newParent) {
498
- let toIndex = newParent.children.findIndex(
499
- (c) => c.id === dropTargetNodeId
500
- );
501
- if (dropPosition === "after") toIndex++;
502
- const newTree = (0, import_core2.moveNode)(
503
- state.tree,
504
- draggedNodeId,
505
- newParent.id,
506
- toIndex
778
+ const updatedTarget = newTree.nodesById.get(dropTargetNodeId);
779
+ if (!updatedTarget || !updatedTarget.parentId) {
780
+ setDragState(INITIAL_DRAG_STATE);
781
+ return;
782
+ }
783
+ const updatedParent = newTree.nodesById.get(updatedTarget.parentId);
784
+ if (!updatedParent) {
785
+ setDragState(INITIAL_DRAG_STATE);
786
+ return;
787
+ }
788
+ let insertIdx = updatedParent.children.findIndex(
789
+ (c) => c.id === dropTargetNodeId
790
+ );
791
+ if (dropPosition === "after") insertIdx++;
792
+ for (let i = 0; i < draggedNodes.length; i++) {
793
+ newTree = (0, import_core3.insertNode)(
794
+ newTree,
795
+ updatedParent.id,
796
+ draggedNodes[i],
797
+ insertIdx + i
507
798
  );
508
- actions.setTree(newTree);
509
799
  }
800
+ actions.setTree(newTree);
510
801
  }
511
802
  setDragState(INITIAL_DRAG_STATE);
512
803
  }, [state.tree, actions]);
@@ -528,6 +819,7 @@ function TreeNodeRow({
528
819
  showValues,
529
820
  showCounts,
530
821
  isFocused,
822
+ onSelectRange,
531
823
  onDragStart,
532
824
  onDragOver,
533
825
  onDragEnd,
@@ -535,19 +827,15 @@ function TreeNodeRow({
535
827
  onContextMenu
536
828
  }) {
537
829
  const { state, actions } = useStudio();
538
- const isSelected = state.selectedNodeId === node.id;
830
+ const isSelected = state.selectedNodeIds.has(node.id);
539
831
  const isExpanded = state.expandedNodeIds.has(node.id);
540
832
  const isContainer = node.type === "object" || node.type === "array";
541
833
  const [hovered, setHovered] = (0, import_react5.useState)(false);
542
834
  const isRoot = node.parentId === null;
543
835
  const isSearchMatch = state.searchMatchNodeIds.has(node.id);
544
836
  const isActiveMatch = state.searchMatches.length > 0 && state.searchMatches[state.searchMatchIndex]?.nodeId === node.id;
545
- const schema = state.schema;
546
- const nodeSchema = schema ? (0, import_core3.getPropertySchema)(schema, node.path) : void 0;
547
- const validation = nodeSchema ? (0, import_core3.validateNode)(node, nodeSchema) : null;
548
- const hasError = validation ? !validation.valid : false;
549
837
  const isDragTarget = dragState.dropTargetNodeId === node.id;
550
- const isDraggedNode = dragState.draggedNodeId === node.id;
838
+ const isDraggedNode = dragState.draggedNodeIds.has(node.id);
551
839
  function displayValue() {
552
840
  if (isContainer) {
553
841
  return node.type === "array" ? `[${node.children.length}]` : `{${node.children.length}}`;
@@ -563,12 +851,12 @@ function TreeNodeRow({
563
851
  const position = e.clientY < midY ? "before" : "after";
564
852
  onDragOver(node.id, position);
565
853
  }
566
- let borderTop = "none";
567
- let borderBottom = "none";
854
+ let borderTopColor = "transparent";
855
+ let borderBottomColor = "transparent";
568
856
  if (isDragTarget && dragState.dropPosition === "before") {
569
- borderTop = "2px solid var(--vj-accent, #007acc)";
857
+ borderTopColor = "var(--vj-accent, #007acc)";
570
858
  } else if (isDragTarget && dragState.dropPosition === "after") {
571
- borderBottom = "2px solid var(--vj-accent, #007acc)";
859
+ borderBottomColor = "var(--vj-accent, #007acc)";
572
860
  }
573
861
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
574
862
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -577,13 +865,24 @@ function TreeNodeRow({
577
865
  role: "treeitem",
578
866
  "aria-selected": isSelected,
579
867
  "aria-expanded": isContainer ? isExpanded : void 0,
580
- onClick: () => actions.selectNode(node.id),
868
+ onClick: (e) => {
869
+ if (e.shiftKey) {
870
+ onSelectRange(node.id);
871
+ } else if (e.metaKey || e.ctrlKey) {
872
+ actions.toggleNodeSelection(node.id);
873
+ } else {
874
+ actions.selectAndDrillDown(node.id);
875
+ }
876
+ },
581
877
  onMouseEnter: () => setHovered(true),
582
878
  onMouseLeave: () => setHovered(false),
583
879
  onContextMenu: (e) => onContextMenu(e, node),
584
880
  draggable: !isRoot,
585
881
  onDragStart: (e) => {
586
882
  e.dataTransfer.effectAllowed = "move";
883
+ if (state.selectedNodeIds.size > 1 && state.selectedNodeIds.has(node.id)) {
884
+ setMultiDragImage(e, state.selectedNodeIds.size);
885
+ }
587
886
  onDragStart(node.id);
588
887
  },
589
888
  onDragOver: handleDragOverEvent,
@@ -597,15 +896,16 @@ function TreeNodeRow({
597
896
  display: "flex",
598
897
  alignItems: "center",
599
898
  gap: 6,
600
- padding: "3px 8px",
899
+ padding: "1px 8px",
601
900
  paddingLeft: 8 + depth * 16,
602
901
  cursor: "pointer",
603
902
  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",
604
903
  minHeight: 28,
605
904
  userSelect: "none",
606
905
  opacity: isDraggedNode ? 0.4 : 1,
607
- borderTop,
608
- borderBottom,
906
+ borderTop: `2px solid ${borderTopColor}`,
907
+ borderBottom: `2px solid ${borderBottomColor}`,
908
+ boxSizing: "border-box",
609
909
  color: isSelected && isFocused ? "var(--vj-text-selected, var(--vj-text, #cccccc))" : "var(--vj-text, #cccccc)"
610
910
  },
611
911
  children: [
@@ -688,6 +988,7 @@ function TreeNodeRow({
688
988
  showValues,
689
989
  showCounts,
690
990
  isFocused,
991
+ onSelectRange,
691
992
  onDragStart,
692
993
  onDragOver,
693
994
  onDragEnd,
@@ -705,25 +1006,34 @@ function TreeView({
705
1006
  }) {
706
1007
  const { state, actions } = useStudio();
707
1008
  const containerRef = (0, import_react5.useRef)(null);
1009
+ const visibleNodes = (0, import_react5.useMemo)(
1010
+ () => getVisibleNodes(state.tree.root, (id) => state.expandedNodeIds.has(id)),
1011
+ [state.tree.root, state.expandedNodeIds]
1012
+ );
708
1013
  const {
709
1014
  dragState,
710
1015
  handleDragStart,
711
1016
  handleDragOver,
712
1017
  handleDragEnd,
713
1018
  handleDrop
714
- } = useDragDrop();
715
- const [contextMenu, setContextMenu] = (0, import_react5.useState)(null);
716
- const visibleNodes = (0, import_react5.useMemo)(
717
- () => getVisibleNodes(state.tree.root, (id) => state.expandedNodeIds.has(id)),
718
- [state.tree.root, state.expandedNodeIds]
1019
+ } = useDragDrop(visibleNodes, state.selectedNodeIds);
1020
+ const handleSelectRange = (0, import_react5.useCallback)(
1021
+ (nodeId) => {
1022
+ actions.setVisibleNodesOverride(visibleNodes);
1023
+ actions.selectNodeRange(nodeId);
1024
+ },
1025
+ [visibleNodes, actions]
719
1026
  );
1027
+ const [contextMenu, setContextMenu] = (0, import_react5.useState)(null);
720
1028
  const handleContextMenu = (0, import_react5.useCallback)(
721
1029
  (e, node) => {
722
1030
  e.preventDefault();
723
- actions.selectNode(node.id);
1031
+ if (!state.selectedNodeIds.has(node.id)) {
1032
+ actions.selectAndDrillDown(node.id);
1033
+ }
724
1034
  setContextMenu({ x: e.clientX, y: e.clientY, node });
725
1035
  },
726
- [actions]
1036
+ [actions, state.selectedNodeIds]
727
1037
  );
728
1038
  const buildContextMenuItems = (0, import_react5.useCallback)(
729
1039
  (node) => {
@@ -767,7 +1077,7 @@ function TreeView({
767
1077
  items.push({
768
1078
  label: "Copy value as JSON",
769
1079
  action: () => {
770
- const val = (0, import_core3.toJson)(node);
1080
+ const val = (0, import_core4.toJson)(node);
771
1081
  const text = typeof val === "string" ? val : JSON.stringify(val, null, 2);
772
1082
  navigator.clipboard.writeText(text).catch(() => {
773
1083
  });
@@ -778,7 +1088,7 @@ function TreeView({
778
1088
  items.push({
779
1089
  label: "Duplicate",
780
1090
  action: () => {
781
- const newTree = (0, import_core3.duplicateNode)(state.tree, node.id);
1091
+ const newTree = (0, import_core4.duplicateNode)(state.tree, node.id);
782
1092
  actions.setTree(newTree);
783
1093
  }
784
1094
  });
@@ -792,7 +1102,7 @@ function TreeView({
792
1102
  ].filter((t) => t !== node.type).map((t) => ({
793
1103
  label: `Change to ${t}`,
794
1104
  action: () => {
795
- const newTree = (0, import_core3.changeType)(state.tree, node.id, t);
1105
+ const newTree = (0, import_core4.changeType)(state.tree, node.id, t);
796
1106
  actions.setTree(newTree);
797
1107
  }
798
1108
  }));
@@ -802,7 +1112,7 @@ function TreeView({
802
1112
  items.push({
803
1113
  label: "Delete",
804
1114
  action: () => {
805
- const newTree = (0, import_core3.removeNode)(state.tree, node.id);
1115
+ const newTree = (0, import_core4.removeNode)(state.tree, node.id);
806
1116
  actions.setTree(newTree);
807
1117
  }
808
1118
  });
@@ -814,19 +1124,31 @@ function TreeView({
814
1124
  const handleKeyDown = (0, import_react5.useCallback)(
815
1125
  (e) => {
816
1126
  const currentIndex = visibleNodes.findIndex(
817
- (n) => n.id === state.selectedNodeId
1127
+ (n) => n.id === state.focusedNodeId
818
1128
  );
819
1129
  switch (e.key) {
820
1130
  case "ArrowDown": {
821
1131
  e.preventDefault();
822
1132
  const next = visibleNodes[currentIndex + 1];
823
- if (next) actions.selectNode(next.id);
1133
+ if (next) {
1134
+ if (e.shiftKey) {
1135
+ handleSelectRange(next.id);
1136
+ } else {
1137
+ actions.selectNode(next.id);
1138
+ }
1139
+ }
824
1140
  break;
825
1141
  }
826
1142
  case "ArrowUp": {
827
1143
  e.preventDefault();
828
1144
  const prev = visibleNodes[currentIndex - 1];
829
- if (prev) actions.selectNode(prev.id);
1145
+ if (prev) {
1146
+ if (e.shiftKey) {
1147
+ handleSelectRange(prev.id);
1148
+ } else {
1149
+ actions.selectNode(prev.id);
1150
+ }
1151
+ }
830
1152
  break;
831
1153
  }
832
1154
  case "ArrowRight": {
@@ -853,17 +1175,47 @@ function TreeView({
853
1175
  }
854
1176
  break;
855
1177
  }
1178
+ case "a": {
1179
+ if (e.metaKey || e.ctrlKey) {
1180
+ e.preventDefault();
1181
+ const ids = computeSelectAllIds(
1182
+ state.tree,
1183
+ state.focusedNodeId,
1184
+ state.selectedNodeIds
1185
+ );
1186
+ if (ids) {
1187
+ actions.setSelection(
1188
+ state.focusedNodeId,
1189
+ ids,
1190
+ state.focusedNodeId
1191
+ );
1192
+ }
1193
+ }
1194
+ break;
1195
+ }
1196
+ case "Escape": {
1197
+ e.preventDefault();
1198
+ if (state.selectedNodeIds.size > 1 && state.focusedNodeId) {
1199
+ actions.selectNode(state.focusedNodeId);
1200
+ } else {
1201
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
1202
+ }
1203
+ break;
1204
+ }
856
1205
  case "Delete":
857
1206
  case "Backspace": {
858
1207
  e.preventDefault();
859
- const toDelete = currentIndex >= 0 ? visibleNodes[currentIndex] : null;
860
- if (toDelete && toDelete.parentId) {
861
- const nextSelect = visibleNodes[currentIndex + 1] ?? visibleNodes[currentIndex - 1];
862
- const newTree = (0, import_core3.removeNode)(state.tree, toDelete.id);
863
- actions.setTree(newTree);
864
- if (nextSelect && nextSelect.id !== toDelete.id) {
865
- actions.selectNode(nextSelect.id);
866
- }
1208
+ const { newTree, nextFocusId } = deleteSelectedNodes(
1209
+ state.tree,
1210
+ state.selectedNodeIds,
1211
+ visibleNodes
1212
+ );
1213
+ if (newTree === state.tree) break;
1214
+ actions.setTree(newTree);
1215
+ if (nextFocusId) {
1216
+ actions.selectNode(nextFocusId);
1217
+ } else {
1218
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
867
1219
  }
868
1220
  break;
869
1221
  }
@@ -871,23 +1223,25 @@ function TreeView({
871
1223
  },
872
1224
  [
873
1225
  visibleNodes,
874
- state.selectedNodeId,
1226
+ state.focusedNodeId,
1227
+ state.selectedNodeIds,
875
1228
  state.expandedNodeIds,
876
1229
  state.tree,
877
- actions
1230
+ actions,
1231
+ handleSelectRange
878
1232
  ]
879
1233
  );
880
1234
  const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
881
1235
  (0, import_react5.useEffect)(() => {
882
- if (state.selectedNodeId && containerRef.current) {
1236
+ if (state.focusedNodeId && containerRef.current) {
883
1237
  const el = containerRef.current.querySelector(
884
- `[data-node-id="${state.selectedNodeId}"]`
1238
+ `[data-node-id="${state.focusedNodeId}"]`
885
1239
  );
886
1240
  if (el) {
887
1241
  el.scrollIntoView({ block: "nearest" });
888
1242
  }
889
1243
  }
890
- }, [state.selectedNodeId]);
1244
+ }, [state.focusedNodeId]);
891
1245
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
892
1246
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
893
1247
  "div",
@@ -917,6 +1271,7 @@ function TreeView({
917
1271
  showValues,
918
1272
  showCounts,
919
1273
  isFocused,
1274
+ onSelectRange: handleSelectRange,
920
1275
  onDragStart: handleDragStart,
921
1276
  onDragOver: handleDragOver,
922
1277
  onDragEnd: handleDragEnd,
@@ -939,8 +1294,8 @@ function TreeView({
939
1294
  }
940
1295
 
941
1296
  // src/form-view.tsx
942
- var import_react7 = require("react");
943
- var import_core4 = require("@visual-json/core");
1297
+ var import_react8 = require("react");
1298
+ var import_core5 = require("@visual-json/core");
944
1299
 
945
1300
  // src/breadcrumbs.tsx
946
1301
  var import_react6 = require("react");
@@ -949,8 +1304,8 @@ var MAX_SUGGESTIONS = 20;
949
1304
  var DROPDOWN_MAX_HEIGHT = 200;
950
1305
  function Breadcrumbs({ className }) {
951
1306
  const { state, actions } = useStudio();
952
- const selectedNode = state.selectedNodeId ? state.tree.nodesById.get(state.selectedNodeId) : null;
953
- const currentPath = selectedNode?.path ?? "/";
1307
+ const drillDownNode = state.drillDownNodeId ? state.tree.nodesById.get(state.drillDownNodeId) : null;
1308
+ const currentPath = drillDownNode?.path ?? "/";
954
1309
  const [inputValue, setInputValue] = (0, import_react6.useState)(currentPath);
955
1310
  const [open, setOpen] = (0, import_react6.useState)(false);
956
1311
  const [highlightIndex, setHighlightIndex] = (0, import_react6.useState)(0);
@@ -980,7 +1335,7 @@ function Breadcrumbs({ className }) {
980
1335
  (path) => {
981
1336
  for (const [id, node] of state.tree.nodesById) {
982
1337
  if (node.path === path) {
983
- actions.selectNode(id);
1338
+ actions.selectAndDrillDown(id);
984
1339
  break;
985
1340
  }
986
1341
  }
@@ -1068,8 +1423,8 @@ function Breadcrumbs({ className }) {
1068
1423
  style: {
1069
1424
  width: "100%",
1070
1425
  boxSizing: "border-box",
1071
- padding: "2px 0",
1072
- fontSize: 12,
1426
+ padding: "3px 0",
1427
+ fontSize: "var(--vj-input-font-size, 13px)",
1073
1428
  fontFamily: "var(--vj-font, monospace)",
1074
1429
  color: "var(--vj-text-muted, #999999)",
1075
1430
  background: "transparent",
@@ -1124,75 +1479,241 @@ function Breadcrumbs({ className }) {
1124
1479
  );
1125
1480
  }
1126
1481
 
1127
- // src/form-view.tsx
1482
+ // src/enum-input.tsx
1483
+ var import_react7 = require("react");
1128
1484
  var import_jsx_runtime5 = require("react/jsx-runtime");
1129
- function getResolvedSchema(schema, rootSchema, path) {
1130
- if (!schema) return void 0;
1131
- const raw = (0, import_core4.getPropertySchema)(schema, path, rootSchema);
1132
- if (!raw) return void 0;
1133
- return (0, import_core4.resolveRef)(raw, rootSchema ?? schema);
1134
- }
1135
- function getValueColor(node) {
1136
- if (node.type === "boolean" || node.type === "null")
1137
- return "var(--vj-boolean, #569cd6)";
1138
- if (node.type === "number") return "var(--vj-number, #b5cea8)";
1139
- return "var(--vj-string, #ce9178)";
1140
- }
1141
- function getDisplayValue(node) {
1142
- if (node.type === "null") return "null";
1143
- if (node.type === "boolean") return String(node.value);
1144
- if (node.value === null || node.value === void 0) return "";
1145
- return String(node.value);
1146
- }
1147
- function FormField({
1148
- node,
1149
- schema,
1150
- rootSchema,
1151
- depth,
1152
- showDescriptions,
1153
- showCounts,
1154
- formSelectedNodeId,
1155
- editingNodeId,
1156
- collapsedIds,
1157
- maxKeyLength,
1158
- maxDepth,
1159
- isFocused,
1160
- dragState,
1161
- onSelect,
1162
- onToggleCollapse,
1163
- onStartEditing,
1164
- onDragStart,
1165
- onDragOver,
1166
- onDragEnd,
1167
- onDrop
1485
+ var DROPDOWN_MAX_HEIGHT2 = 200;
1486
+ function EnumInput({
1487
+ enumValues,
1488
+ value,
1489
+ onValueChange,
1490
+ inputRef,
1491
+ inputStyle
1168
1492
  }) {
1169
- const { state, actions } = useStudio();
1170
- const isContainer = node.type === "object" || node.type === "array";
1171
- const collapsed = collapsedIds.has(node.id);
1172
- const isSelected = formSelectedNodeId === node.id;
1173
- const isEditing = editingNodeId === node.id;
1174
- const propSchema = getResolvedSchema(schema, rootSchema, node.path);
1175
- const isRequired = checkRequired(node, schema, rootSchema);
1176
- const [hovered, setHovered] = (0, import_react7.useState)(false);
1177
- const isRoot = node.parentId === null;
1178
- const isDragTarget = dragState.dropTargetNodeId === node.id;
1179
- const isDraggedNode = dragState.draggedNodeId === node.id;
1180
- function handleDragOverEvent(e) {
1181
- e.preventDefault();
1182
- const rect = e.currentTarget.getBoundingClientRect();
1183
- const midY = rect.top + rect.height / 2;
1184
- onDragOver(node.id, e.clientY < midY ? "before" : "after");
1185
- }
1186
- let borderTop = "none";
1187
- let borderBottom = "none";
1188
- if (isDragTarget && dragState.dropPosition === "before") {
1189
- borderTop = "2px solid var(--vj-accent, #007acc)";
1190
- } else if (isDragTarget && dragState.dropPosition === "after") {
1191
- borderBottom = "2px solid var(--vj-accent, #007acc)";
1192
- }
1193
- const valueRef = (0, import_react7.useRef)(null);
1194
- const keyRef = (0, import_react7.useRef)(null);
1493
+ const [inputValue, setInputValue] = (0, import_react7.useState)(value);
1494
+ const [open, setOpen] = (0, import_react7.useState)(false);
1495
+ const [highlightIndex, setHighlightIndex] = (0, import_react7.useState)(0);
1496
+ const listRef = (0, import_react7.useRef)(null);
1497
+ const wrapperRef = (0, import_react7.useRef)(null);
1498
+ (0, import_react7.useEffect)(() => {
1499
+ setInputValue(value);
1500
+ }, [value]);
1501
+ const suggestions = (0, import_react7.useMemo)(
1502
+ () => enumValues.map((v) => String(v)),
1503
+ [enumValues]
1504
+ );
1505
+ (0, import_react7.useEffect)(() => {
1506
+ setHighlightIndex(0);
1507
+ }, [suggestions]);
1508
+ const selectValue = (0, import_react7.useCallback)(
1509
+ (val) => {
1510
+ onValueChange(val);
1511
+ setInputValue(val);
1512
+ setOpen(false);
1513
+ },
1514
+ [onValueChange]
1515
+ );
1516
+ const handleKeyDown = (0, import_react7.useCallback)(
1517
+ (e) => {
1518
+ if (!open) {
1519
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
1520
+ e.preventDefault();
1521
+ e.stopPropagation();
1522
+ setOpen(true);
1523
+ }
1524
+ return;
1525
+ }
1526
+ switch (e.key) {
1527
+ case "ArrowDown":
1528
+ e.preventDefault();
1529
+ e.stopPropagation();
1530
+ setHighlightIndex((i) => Math.min(i + 1, suggestions.length - 1));
1531
+ break;
1532
+ case "ArrowUp":
1533
+ e.preventDefault();
1534
+ e.stopPropagation();
1535
+ setHighlightIndex((i) => Math.max(i - 1, 0));
1536
+ break;
1537
+ case "Enter":
1538
+ e.preventDefault();
1539
+ e.stopPropagation();
1540
+ if (suggestions.length > 0 && highlightIndex < suggestions.length) {
1541
+ selectValue(suggestions[highlightIndex]);
1542
+ }
1543
+ break;
1544
+ case "Escape":
1545
+ e.preventDefault();
1546
+ e.stopPropagation();
1547
+ setInputValue(value);
1548
+ setOpen(false);
1549
+ break;
1550
+ case "Tab":
1551
+ setInputValue(value);
1552
+ setOpen(false);
1553
+ break;
1554
+ }
1555
+ },
1556
+ [open, suggestions, highlightIndex, value, selectValue]
1557
+ );
1558
+ (0, import_react7.useEffect)(() => {
1559
+ const el = listRef.current;
1560
+ if (!el || !open) return;
1561
+ const item = el.children[highlightIndex];
1562
+ item?.scrollIntoView({ block: "nearest" });
1563
+ }, [highlightIndex, open]);
1195
1564
  (0, import_react7.useEffect)(() => {
1565
+ function handleClickOutside(e) {
1566
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
1567
+ setInputValue(value);
1568
+ setOpen(false);
1569
+ }
1570
+ }
1571
+ document.addEventListener("mousedown", handleClickOutside);
1572
+ return () => document.removeEventListener("mousedown", handleClickOutside);
1573
+ }, [value]);
1574
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1575
+ "div",
1576
+ {
1577
+ ref: wrapperRef,
1578
+ style: { position: "relative", flex: 1, minWidth: 0 },
1579
+ children: [
1580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1581
+ "input",
1582
+ {
1583
+ ref: inputRef,
1584
+ value: inputValue,
1585
+ onChange: (e) => {
1586
+ setInputValue(e.target.value);
1587
+ if (!open) setOpen(true);
1588
+ },
1589
+ onFocus: () => setOpen(true),
1590
+ onKeyDown: handleKeyDown,
1591
+ onClick: (e) => e.stopPropagation(),
1592
+ spellCheck: false,
1593
+ autoComplete: "off",
1594
+ style: inputStyle
1595
+ }
1596
+ ),
1597
+ open && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1598
+ "div",
1599
+ {
1600
+ ref: listRef,
1601
+ style: {
1602
+ position: "absolute",
1603
+ top: "calc(100% + 4px)",
1604
+ left: -32,
1605
+ right: 0,
1606
+ zIndex: 50,
1607
+ maxHeight: DROPDOWN_MAX_HEIGHT2,
1608
+ overflowY: "auto",
1609
+ backgroundColor: "var(--vj-bg-panel, #252526)",
1610
+ border: "1px solid var(--vj-border, #333333)",
1611
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)"
1612
+ },
1613
+ children: suggestions.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1614
+ "div",
1615
+ {
1616
+ onMouseDown: (e) => {
1617
+ e.preventDefault();
1618
+ selectValue(s);
1619
+ },
1620
+ onMouseEnter: () => setHighlightIndex(i),
1621
+ style: {
1622
+ padding: "4px 12px",
1623
+ fontSize: 13,
1624
+ fontFamily: "var(--vj-font, monospace)",
1625
+ display: "flex",
1626
+ alignItems: "center",
1627
+ gap: 6,
1628
+ color: i === highlightIndex ? "var(--vj-text, #cccccc)" : "var(--vj-text-muted, #888888)",
1629
+ backgroundColor: i === highlightIndex ? "var(--vj-bg-hover, #2a2d2e)" : "transparent",
1630
+ cursor: "pointer",
1631
+ whiteSpace: "nowrap",
1632
+ overflow: "hidden",
1633
+ textOverflow: "ellipsis"
1634
+ },
1635
+ children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { width: 14, flexShrink: 0, fontSize: 12 }, children: s === value ? "\u2713" : "" }),
1637
+ s
1638
+ ]
1639
+ },
1640
+ s
1641
+ ))
1642
+ }
1643
+ )
1644
+ ]
1645
+ }
1646
+ );
1647
+ }
1648
+
1649
+ // src/form-view.tsx
1650
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1651
+ function getResolvedSchema(schema, rootSchema, path) {
1652
+ if (!schema) return void 0;
1653
+ const raw = (0, import_core5.getPropertySchema)(schema, path, rootSchema);
1654
+ if (!raw) return void 0;
1655
+ return (0, import_core5.resolveRef)(raw, rootSchema ?? schema);
1656
+ }
1657
+ function getValueColor(node) {
1658
+ if (node.type === "boolean" || node.type === "null")
1659
+ return "var(--vj-boolean, #569cd6)";
1660
+ if (node.type === "number") return "var(--vj-number, #b5cea8)";
1661
+ return "var(--vj-string, #ce9178)";
1662
+ }
1663
+ function getDisplayValue(node) {
1664
+ if (node.type === "null") return "null";
1665
+ if (node.type === "boolean") return String(node.value);
1666
+ if (node.value === null || node.value === void 0) return "";
1667
+ return String(node.value);
1668
+ }
1669
+ function FormField({
1670
+ node,
1671
+ schema,
1672
+ rootSchema,
1673
+ depth,
1674
+ showDescriptions,
1675
+ showCounts,
1676
+ editingNodeId,
1677
+ collapsedIds,
1678
+ maxKeyLength,
1679
+ maxDepth,
1680
+ isFocused,
1681
+ dragState,
1682
+ onSelect,
1683
+ onToggleCollapse,
1684
+ onStartEditing,
1685
+ onDragStart,
1686
+ onDragOver,
1687
+ onDragEnd,
1688
+ onDrop
1689
+ }) {
1690
+ const { state, actions } = useStudio();
1691
+ const isContainer = node.type === "object" || node.type === "array";
1692
+ const collapsed = collapsedIds.has(node.id);
1693
+ const isSelected = state.selectedNodeIds.has(node.id);
1694
+ const isEditing = editingNodeId === node.id;
1695
+ const propSchema = getResolvedSchema(schema, rootSchema, node.path);
1696
+ const isRequired = checkRequired(node, schema, rootSchema);
1697
+ const [hovered, setHovered] = (0, import_react8.useState)(false);
1698
+ const isRoot = node.parentId === null;
1699
+ const isDragTarget = dragState.dropTargetNodeId === node.id;
1700
+ const isDraggedNode = dragState.draggedNodeIds.has(node.id);
1701
+ function handleDragOverEvent(e) {
1702
+ e.preventDefault();
1703
+ const rect = e.currentTarget.getBoundingClientRect();
1704
+ const midY = rect.top + rect.height / 2;
1705
+ onDragOver(node.id, e.clientY < midY ? "before" : "after");
1706
+ }
1707
+ let borderTopColor = "transparent";
1708
+ let borderBottomColor = "transparent";
1709
+ if (isDragTarget && dragState.dropPosition === "before") {
1710
+ borderTopColor = "var(--vj-accent, #007acc)";
1711
+ } else if (isDragTarget && dragState.dropPosition === "after") {
1712
+ borderBottomColor = "var(--vj-accent, #007acc)";
1713
+ }
1714
+ const valueRef = (0, import_react8.useRef)(null);
1715
+ const keyRef = (0, import_react8.useRef)(null);
1716
+ (0, import_react8.useEffect)(() => {
1196
1717
  if (!isEditing) return;
1197
1718
  if (!isContainer) {
1198
1719
  const hasValue = node.value !== null && node.value !== void 0 && node.value !== "";
@@ -1205,7 +1726,7 @@ function FormField({
1205
1726
  keyRef.current.focus();
1206
1727
  }
1207
1728
  }, [isEditing, isContainer, node.value]);
1208
- const handleValueChange = (0, import_react7.useCallback)(
1729
+ const handleValueChange = (0, import_react8.useCallback)(
1209
1730
  (newValue) => {
1210
1731
  let parsed;
1211
1732
  if (propSchema?.type === "boolean" || newValue === "true" || newValue === "false") {
@@ -1218,43 +1739,45 @@ function FormField({
1218
1739
  } else {
1219
1740
  parsed = newValue;
1220
1741
  }
1221
- const newTree = (0, import_core4.setValue)(state.tree, node.id, parsed);
1742
+ const newTree = (0, import_core5.setValue)(state.tree, node.id, parsed);
1222
1743
  actions.setTree(newTree);
1223
1744
  },
1224
1745
  [state.tree, node.id, node.type, actions, propSchema]
1225
1746
  );
1226
- const handleKeyChange = (0, import_react7.useCallback)(
1747
+ const handleKeyChange = (0, import_react8.useCallback)(
1227
1748
  (newKey) => {
1228
- const newTree = (0, import_core4.setKey)(state.tree, node.id, newKey);
1749
+ const newTree = (0, import_core5.setKey)(state.tree, node.id, newKey);
1229
1750
  actions.setTree(newTree);
1230
1751
  },
1231
1752
  [state.tree, node.id, actions]
1232
1753
  );
1233
- const handleRemove = (0, import_react7.useCallback)(() => {
1234
- const newTree = (0, import_core4.removeNode)(state.tree, node.id);
1754
+ const handleRemove = (0, import_react8.useCallback)(() => {
1755
+ const newTree = (0, import_core5.removeNode)(state.tree, node.id);
1235
1756
  actions.setTree(newTree);
1236
1757
  }, [state.tree, node.id, actions]);
1237
- const handleAddChild = (0, import_react7.useCallback)(() => {
1758
+ const handleAddChild = (0, import_react8.useCallback)(() => {
1238
1759
  const key = node.type === "array" ? String(node.children.length) : `newKey${node.children.length}`;
1239
- const newTree = (0, import_core4.addProperty)(state.tree, node.id, key, "");
1760
+ const newTree = (0, import_core5.addProperty)(state.tree, node.id, key, "");
1240
1761
  actions.setTree(newTree);
1241
1762
  }, [state.tree, node.id, node.type, node.children.length, actions]);
1242
1763
  const description = propSchema?.description;
1243
- const defaultVal = propSchema?.default;
1244
1764
  const isDeprecated = propSchema?.deprecated;
1245
1765
  const fieldTitle = propSchema?.title;
1246
1766
  const parentIsObject = node.parentId && state.tree.nodesById.get(node.parentId)?.type === "object";
1247
1767
  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";
1248
1768
  const rowColor = isSelected && isFocused ? "var(--vj-text-selected, var(--vj-text, #cccccc))" : "var(--vj-text, #cccccc)";
1249
1769
  if (isContainer) {
1250
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1251
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1770
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1771
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1252
1772
  "div",
1253
1773
  {
1254
1774
  "data-form-node-id": node.id,
1255
1775
  draggable: !isRoot,
1256
1776
  onDragStart: (e) => {
1257
1777
  e.dataTransfer.effectAllowed = "move";
1778
+ if (state.selectedNodeIds.size > 1 && state.selectedNodeIds.has(node.id)) {
1779
+ setMultiDragImage(e, state.selectedNodeIds.size);
1780
+ }
1258
1781
  onDragStart(node.id);
1259
1782
  },
1260
1783
  onDragOver: handleDragOverEvent,
@@ -1267,26 +1790,27 @@ function FormField({
1267
1790
  display: "flex",
1268
1791
  alignItems: "center",
1269
1792
  gap: 6,
1270
- padding: "3px 8px",
1793
+ padding: "1px 8px",
1271
1794
  paddingLeft: 8 + depth * 16,
1272
1795
  cursor: "pointer",
1273
1796
  backgroundColor: rowBg,
1274
1797
  color: rowColor,
1275
1798
  height: 28,
1799
+ boxSizing: "border-box",
1276
1800
  userSelect: "none",
1277
1801
  opacity: isDeprecated ? 0.5 : isDraggedNode ? 0.4 : 1,
1278
- borderTop,
1279
- borderBottom
1802
+ borderTop: `2px solid ${borderTopColor}`,
1803
+ borderBottom: `2px solid ${borderBottomColor}`
1280
1804
  },
1281
1805
  onClick: (e) => {
1282
1806
  e.stopPropagation();
1283
- onSelect(node.id);
1807
+ onSelect(node.id, e);
1284
1808
  },
1285
1809
  onDoubleClick: () => onToggleCollapse(node.id),
1286
1810
  onMouseEnter: () => setHovered(true),
1287
1811
  onMouseLeave: () => setHovered(false),
1288
1812
  children: [
1289
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1813
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1290
1814
  "button",
1291
1815
  {
1292
1816
  onClick: (e) => {
@@ -1311,7 +1835,7 @@ function FormField({
1311
1835
  children: "\u25B6"
1312
1836
  }
1313
1837
  ),
1314
- isEditing && !isRoot ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1838
+ isEditing && !isRoot ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1315
1839
  "input",
1316
1840
  {
1317
1841
  ref: keyRef,
@@ -1323,7 +1847,7 @@ function FormField({
1323
1847
  border: "none",
1324
1848
  color: "inherit",
1325
1849
  fontFamily: "var(--vj-font, monospace)",
1326
- fontSize: 13,
1850
+ fontSize: "var(--vj-input-font-size, 13px)",
1327
1851
  fontWeight: 500,
1328
1852
  padding: 0,
1329
1853
  outline: "none",
@@ -1331,12 +1855,12 @@ function FormField({
1331
1855
  width: `calc(${(maxDepth - depth) * 16}px + ${maxKeyLength}ch)`
1332
1856
  }
1333
1857
  }
1334
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1858
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1335
1859
  "span",
1336
1860
  {
1337
1861
  style: {
1338
1862
  color: !isRoot && !parentIsObject && !isSelected ? "var(--vj-text-muted, #888888)" : "inherit",
1339
- fontSize: 13,
1863
+ fontSize: "var(--vj-input-font-size, 13px)",
1340
1864
  fontFamily: "var(--vj-font, monospace)",
1341
1865
  fontWeight: 500,
1342
1866
  flexShrink: 0,
@@ -1346,7 +1870,7 @@ function FormField({
1346
1870
  children: isRoot ? "/" : getDisplayKey(node, state.tree)
1347
1871
  }
1348
1872
  ),
1349
- showDescriptions && fieldTitle && !isSelected && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1873
+ showDescriptions && fieldTitle && !isSelected && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1350
1874
  "span",
1351
1875
  {
1352
1876
  style: {
@@ -1357,7 +1881,7 @@ function FormField({
1357
1881
  children: fieldTitle
1358
1882
  }
1359
1883
  ),
1360
- hovered && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1884
+ hovered && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1361
1885
  "button",
1362
1886
  {
1363
1887
  onClick: (e) => {
@@ -1379,7 +1903,7 @@ function FormField({
1379
1903
  ]
1380
1904
  }
1381
1905
  ),
1382
- showCounts && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1906
+ showCounts && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1383
1907
  "span",
1384
1908
  {
1385
1909
  style: {
@@ -1391,7 +1915,7 @@ function FormField({
1391
1915
  children: node.type === "array" ? `${node.children.length} items` : `${node.children.length} properties`
1392
1916
  }
1393
1917
  ),
1394
- !isRoot && isEditing && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1918
+ !isRoot && isEditing && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1395
1919
  "button",
1396
1920
  {
1397
1921
  onClick: (e) => {
@@ -1415,7 +1939,7 @@ function FormField({
1415
1939
  ]
1416
1940
  }
1417
1941
  ),
1418
- showDescriptions && description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1942
+ showDescriptions && description && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1419
1943
  "div",
1420
1944
  {
1421
1945
  style: {
@@ -1428,7 +1952,7 @@ function FormField({
1428
1952
  children: description
1429
1953
  }
1430
1954
  ),
1431
- !collapsed && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1955
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1432
1956
  FormField,
1433
1957
  {
1434
1958
  node: child,
@@ -1437,7 +1961,6 @@ function FormField({
1437
1961
  depth: depth + 1,
1438
1962
  showDescriptions,
1439
1963
  showCounts,
1440
- formSelectedNodeId,
1441
1964
  editingNodeId,
1442
1965
  collapsedIds,
1443
1966
  maxKeyLength,
@@ -1458,13 +1981,16 @@ function FormField({
1458
1981
  }
1459
1982
  const displayValue = getDisplayValue(node);
1460
1983
  const valueColor = getValueColor(node);
1461
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1984
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1462
1985
  "div",
1463
1986
  {
1464
1987
  "data-form-node-id": node.id,
1465
1988
  draggable: !isRoot,
1466
1989
  onDragStart: (e) => {
1467
1990
  e.dataTransfer.effectAllowed = "move";
1991
+ if (state.selectedNodeIds.size > 1 && state.selectedNodeIds.has(node.id)) {
1992
+ setMultiDragImage(e, state.selectedNodeIds.size);
1993
+ }
1468
1994
  onDragStart(node.id);
1469
1995
  },
1470
1996
  onDragOver: handleDragOverEvent,
@@ -1477,27 +2003,28 @@ function FormField({
1477
2003
  display: "flex",
1478
2004
  alignItems: "center",
1479
2005
  gap: 6,
1480
- padding: "3px 8px",
2006
+ padding: "1px 8px",
1481
2007
  paddingLeft: 8 + depth * 16,
1482
2008
  cursor: "pointer",
1483
2009
  backgroundColor: rowBg,
1484
2010
  color: rowColor,
1485
2011
  height: 28,
2012
+ boxSizing: "border-box",
1486
2013
  userSelect: "none",
1487
2014
  opacity: isDeprecated ? 0.5 : isDraggedNode ? 0.4 : 1,
1488
- borderTop,
1489
- borderBottom
2015
+ borderTop: `2px solid ${borderTopColor}`,
2016
+ borderBottom: `2px solid ${borderBottomColor}`
1490
2017
  },
1491
2018
  onClick: (e) => {
1492
2019
  e.stopPropagation();
1493
- onSelect(node.id);
2020
+ onSelect(node.id, e);
1494
2021
  },
1495
2022
  onDoubleClick: () => onStartEditing(node.id),
1496
2023
  onMouseEnter: () => setHovered(true),
1497
2024
  onMouseLeave: () => setHovered(false),
1498
2025
  children: [
1499
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { width: 16, flexShrink: 0 } }),
1500
- isEditing && parentIsObject ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2026
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { width: 16, flexShrink: 0 } }),
2027
+ isEditing && parentIsObject ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1501
2028
  "input",
1502
2029
  {
1503
2030
  ref: keyRef,
@@ -1515,19 +2042,19 @@ function FormField({
1515
2042
  border: "none",
1516
2043
  color: "inherit",
1517
2044
  fontFamily: "var(--vj-font, monospace)",
1518
- fontSize: 13,
2045
+ fontSize: "var(--vj-input-font-size, 13px)",
1519
2046
  padding: 0,
1520
2047
  flexShrink: 0,
1521
2048
  outline: "none",
1522
2049
  width: `calc(${(maxDepth - depth) * 16}px + ${maxKeyLength}ch)`
1523
2050
  }
1524
2051
  }
1525
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2052
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1526
2053
  "span",
1527
2054
  {
1528
2055
  style: {
1529
2056
  color: !parentIsObject && !isSelected ? "var(--vj-text-muted, #888888)" : "inherit",
1530
- fontSize: 13,
2057
+ fontSize: "var(--vj-input-font-size, 13px)",
1531
2058
  fontFamily: "var(--vj-font, monospace)",
1532
2059
  flexShrink: 0,
1533
2060
  display: "inline-block",
@@ -1536,7 +2063,7 @@ function FormField({
1536
2063
  children: getDisplayKey(node, state.tree)
1537
2064
  }
1538
2065
  ),
1539
- isRequired && !isSelected && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2066
+ isRequired && !isSelected && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1540
2067
  "span",
1541
2068
  {
1542
2069
  style: {
@@ -1547,7 +2074,7 @@ function FormField({
1547
2074
  children: "*"
1548
2075
  }
1549
2076
  ),
1550
- isEditing ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2077
+ isEditing ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1551
2078
  "div",
1552
2079
  {
1553
2080
  style: {
@@ -1566,12 +2093,12 @@ function FormField({
1566
2093
  valueColor
1567
2094
  )
1568
2095
  }
1569
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2096
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1570
2097
  "span",
1571
2098
  {
1572
2099
  style: {
1573
2100
  color: valueColor,
1574
- fontSize: 13,
2101
+ fontSize: "var(--vj-input-font-size, 13px)",
1575
2102
  fontFamily: "var(--vj-font, monospace)",
1576
2103
  overflow: "hidden",
1577
2104
  textOverflow: "ellipsis",
@@ -1581,7 +2108,7 @@ function FormField({
1581
2108
  children: displayValue
1582
2109
  }
1583
2110
  ),
1584
- isEditing && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2111
+ isEditing && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1585
2112
  "button",
1586
2113
  {
1587
2114
  onClick: (e) => {
@@ -1612,54 +2139,44 @@ function renderEditInput(node, propSchema, displayValue, handleValueChange, inpu
1612
2139
  background: "none",
1613
2140
  border: "none",
1614
2141
  fontFamily: "var(--vj-font, monospace)",
1615
- fontSize: 13,
2142
+ fontSize: "var(--vj-input-font-size, 13px)",
1616
2143
  padding: 0,
1617
2144
  flex: 1,
1618
2145
  outline: "none",
1619
2146
  color: valueColor
1620
2147
  };
1621
2148
  if (node.type === "boolean") {
1622
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1623
- "select",
2149
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2150
+ EnumInput,
1624
2151
  {
1625
- ref: inputRef,
2152
+ enumValues: ["true", "false"],
1626
2153
  value: String(node.value),
1627
- onChange: (e) => handleValueChange(e.target.value),
1628
- onClick: (e) => e.stopPropagation(),
1629
- style: { ...inputStyle, cursor: "pointer" },
1630
- children: [
1631
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "true", children: "true" }),
1632
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "false", children: "false" })
1633
- ]
2154
+ onValueChange: handleValueChange,
2155
+ inputRef,
2156
+ inputStyle
1634
2157
  }
1635
2158
  );
1636
2159
  }
1637
2160
  if (hasEnumValues && propSchema?.enum) {
1638
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1639
- "select",
2161
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2162
+ EnumInput,
1640
2163
  {
1641
- ref: inputRef,
2164
+ enumValues: propSchema.enum,
1642
2165
  value: displayValue,
1643
- onChange: (e) => handleValueChange(e.target.value),
1644
- onClick: (e) => e.stopPropagation(),
1645
- style: { ...inputStyle, cursor: "pointer" },
1646
- children: [
1647
- !propSchema.enum.some(
1648
- (v) => JSON.stringify(v) === JSON.stringify(node.value)
1649
- ) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: displayValue, children: displayValue || "(empty)" }),
1650
- propSchema.enum.map((v, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: String(v), children: String(v) }, i))
1651
- ]
2166
+ onValueChange: handleValueChange,
2167
+ inputRef,
2168
+ inputStyle
1652
2169
  }
1653
2170
  );
1654
2171
  }
1655
2172
  if (node.type === "null") {
1656
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2173
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1657
2174
  "span",
1658
2175
  {
1659
2176
  style: {
1660
2177
  color: "var(--vj-boolean, #569cd6)",
1661
2178
  fontFamily: "var(--vj-font, monospace)",
1662
- fontSize: 13,
2179
+ fontSize: "var(--vj-input-font-size, 13px)",
1663
2180
  fontStyle: "italic",
1664
2181
  flex: 1
1665
2182
  },
@@ -1667,7 +2184,7 @@ function renderEditInput(node, propSchema, displayValue, handleValueChange, inpu
1667
2184
  }
1668
2185
  );
1669
2186
  }
1670
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2187
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1671
2188
  "input",
1672
2189
  {
1673
2190
  ref: inputRef,
@@ -1692,35 +2209,35 @@ function FormView({
1692
2209
  }) {
1693
2210
  const { state, actions } = useStudio();
1694
2211
  const rootSchema = state.schema ?? void 0;
1695
- const selectedNode = state.selectedNodeId ? state.tree.nodesById.get(state.selectedNodeId) : null;
1696
- const displayNode = selectedNode ?? state.tree.root;
1697
- const [formSelectedNodeId, setFormSelectedNodeId] = (0, import_react7.useState)(
1698
- null
1699
- );
1700
- const [editingNodeId, setEditingNodeId] = (0, import_react7.useState)(null);
1701
- const preEditTreeRef = (0, import_react7.useRef)(null);
1702
- const [collapsedIds, setCollapsedIds] = (0, import_react7.useState)(
2212
+ const drillDownNode = state.drillDownNodeId ? state.tree.nodesById.get(state.drillDownNodeId) : null;
2213
+ const displayNode = drillDownNode ?? state.tree.root;
2214
+ const [editingNodeId, setEditingNodeId] = (0, import_react8.useState)(null);
2215
+ const preEditTreeRef = (0, import_react8.useRef)(null);
2216
+ const [collapsedIds, setCollapsedIds] = (0, import_react8.useState)(
1703
2217
  () => /* @__PURE__ */ new Set()
1704
2218
  );
1705
- const containerRef = (0, import_react7.useRef)(null);
1706
- const [isFocused, setIsFocused] = (0, import_react7.useState)(false);
1707
- const {
1708
- dragState,
1709
- handleDragStart,
1710
- handleDragOver,
1711
- handleDragEnd,
1712
- handleDrop
1713
- } = useDragDrop();
1714
- (0, import_react7.useEffect)(() => {
1715
- setFormSelectedNodeId(null);
2219
+ const containerRef = (0, import_react8.useRef)(null);
2220
+ const [isFocused, setIsFocused] = (0, import_react8.useState)(false);
2221
+ (0, import_react8.useEffect)(() => {
1716
2222
  setEditingNodeId(null);
1717
2223
  setCollapsedIds(/* @__PURE__ */ new Set());
1718
2224
  }, [displayNode.id]);
1719
- const visibleNodes = (0, import_react7.useMemo)(
2225
+ const visibleNodes = (0, import_react8.useMemo)(
1720
2226
  () => getVisibleNodes(displayNode, (id) => !collapsedIds.has(id)),
1721
2227
  [displayNode, collapsedIds]
1722
2228
  );
1723
- const { maxKeyLength, maxDepth } = (0, import_react7.useMemo)(() => {
2229
+ const {
2230
+ dragState,
2231
+ handleDragStart,
2232
+ handleDragOver,
2233
+ handleDragEnd,
2234
+ handleDrop
2235
+ } = useDragDrop(visibleNodes, state.selectedNodeIds);
2236
+ (0, import_react8.useEffect)(() => {
2237
+ actions.setVisibleNodesOverride(visibleNodes);
2238
+ return () => actions.setVisibleNodesOverride(null);
2239
+ }, [visibleNodes, actions]);
2240
+ const { maxKeyLength, maxDepth } = (0, import_react8.useMemo)(() => {
1724
2241
  let maxKey = 1;
1725
2242
  let maxD = 0;
1726
2243
  const baseSegments = displayNode.path === "/" ? 0 : displayNode.path.split("/").filter(Boolean).length;
@@ -1733,11 +2250,21 @@ function FormView({
1733
2250
  }
1734
2251
  return { maxKeyLength: maxKey, maxDepth: maxD };
1735
2252
  }, [visibleNodes, displayNode.path, state.tree]);
1736
- const handleSelect = (0, import_react7.useCallback)((nodeId) => {
1737
- setFormSelectedNodeId(nodeId);
1738
- setEditingNodeId(null);
1739
- }, []);
1740
- const handleToggleCollapse = (0, import_react7.useCallback)((nodeId) => {
2253
+ const handleSelect = (0, import_react8.useCallback)(
2254
+ (nodeId, e) => {
2255
+ setEditingNodeId(null);
2256
+ if (e.shiftKey) {
2257
+ actions.setVisibleNodesOverride(visibleNodes);
2258
+ actions.selectNodeRange(nodeId);
2259
+ } else if (e.metaKey || e.ctrlKey) {
2260
+ actions.toggleNodeSelection(nodeId);
2261
+ } else {
2262
+ actions.selectNode(nodeId);
2263
+ }
2264
+ },
2265
+ [actions, visibleNodes]
2266
+ );
2267
+ const handleToggleCollapse = (0, import_react8.useCallback)((nodeId) => {
1741
2268
  setCollapsedIds((prev) => {
1742
2269
  const next = new Set(prev);
1743
2270
  if (next.has(nodeId)) {
@@ -1748,14 +2275,14 @@ function FormView({
1748
2275
  return next;
1749
2276
  });
1750
2277
  }, []);
1751
- const handleStartEditing = (0, import_react7.useCallback)(
2278
+ const handleStartEditing = (0, import_react8.useCallback)(
1752
2279
  (nodeId) => {
1753
2280
  preEditTreeRef.current = state.tree;
1754
2281
  setEditingNodeId(nodeId);
1755
2282
  },
1756
2283
  [state.tree]
1757
2284
  );
1758
- const scrollToNode = (0, import_react7.useCallback)((nodeId) => {
2285
+ const scrollToNode = (0, import_react8.useCallback)((nodeId) => {
1759
2286
  requestAnimationFrame(() => {
1760
2287
  const el = containerRef.current?.querySelector(
1761
2288
  `[data-form-node-id="${nodeId}"]`
@@ -1763,7 +2290,7 @@ function FormView({
1763
2290
  el?.scrollIntoView({ block: "nearest" });
1764
2291
  });
1765
2292
  }, []);
1766
- const handleKeyDown = (0, import_react7.useCallback)(
2293
+ const handleKeyDown = (0, import_react8.useCallback)(
1767
2294
  (e) => {
1768
2295
  if (editingNodeId) {
1769
2296
  if (e.key === "Escape") {
@@ -1784,15 +2311,23 @@ function FormView({
1784
2311
  }
1785
2312
  return;
1786
2313
  }
1787
- const currentIndex = visibleNodes.findIndex(
1788
- (n) => n.id === formSelectedNodeId
2314
+ let currentIndex = visibleNodes.findIndex(
2315
+ (n) => n.id === state.focusedNodeId
1789
2316
  );
2317
+ if (currentIndex === -1 && visibleNodes.length > 0) {
2318
+ currentIndex = 0;
2319
+ }
1790
2320
  switch (e.key) {
1791
2321
  case "ArrowDown": {
1792
2322
  e.preventDefault();
1793
2323
  const next = visibleNodes[currentIndex + 1];
1794
2324
  if (next) {
1795
- setFormSelectedNodeId(next.id);
2325
+ if (e.shiftKey) {
2326
+ actions.setVisibleNodesOverride(visibleNodes);
2327
+ actions.selectNodeRange(next.id);
2328
+ } else {
2329
+ actions.selectNode(next.id);
2330
+ }
1796
2331
  scrollToNode(next.id);
1797
2332
  }
1798
2333
  break;
@@ -1801,7 +2336,12 @@ function FormView({
1801
2336
  e.preventDefault();
1802
2337
  const prev = visibleNodes[currentIndex - 1];
1803
2338
  if (prev) {
1804
- setFormSelectedNodeId(prev.id);
2339
+ if (e.shiftKey) {
2340
+ actions.setVisibleNodesOverride(visibleNodes);
2341
+ actions.selectNodeRange(prev.id);
2342
+ } else {
2343
+ actions.selectNode(prev.id);
2344
+ }
1805
2345
  scrollToNode(prev.id);
1806
2346
  }
1807
2347
  break;
@@ -1817,7 +2357,7 @@ function FormView({
1817
2357
  return next;
1818
2358
  });
1819
2359
  } else if (node.children.length > 0) {
1820
- setFormSelectedNodeId(node.children[0].id);
2360
+ actions.selectNode(node.children[0].id);
1821
2361
  scrollToNode(node.children[0].id);
1822
2362
  }
1823
2363
  }
@@ -1839,7 +2379,7 @@ function FormView({
1839
2379
  (n) => n.id === current.parentId
1840
2380
  );
1841
2381
  if (parentInVisible) {
1842
- setFormSelectedNodeId(parentInVisible.id);
2382
+ actions.selectNode(parentInVisible.id);
1843
2383
  scrollToNode(parentInVisible.id);
1844
2384
  }
1845
2385
  }
@@ -1847,30 +2387,54 @@ function FormView({
1847
2387
  }
1848
2388
  case "Enter": {
1849
2389
  e.preventDefault();
1850
- if (formSelectedNodeId) {
2390
+ if (state.focusedNodeId) {
1851
2391
  preEditTreeRef.current = state.tree;
1852
- setEditingNodeId(formSelectedNodeId);
2392
+ actions.selectNode(state.focusedNodeId);
2393
+ setEditingNodeId(state.focusedNodeId);
2394
+ }
2395
+ break;
2396
+ }
2397
+ case "a": {
2398
+ if (e.metaKey || e.ctrlKey) {
2399
+ e.preventDefault();
2400
+ const ids = computeSelectAllIds(
2401
+ state.tree,
2402
+ state.focusedNodeId,
2403
+ state.selectedNodeIds
2404
+ );
2405
+ if (ids) {
2406
+ actions.setSelection(
2407
+ state.focusedNodeId,
2408
+ ids,
2409
+ state.focusedNodeId
2410
+ );
2411
+ }
1853
2412
  }
1854
2413
  break;
1855
2414
  }
1856
2415
  case "Escape": {
1857
2416
  e.preventDefault();
1858
- setEditingNodeId(null);
2417
+ if (state.selectedNodeIds.size > 1 && state.focusedNodeId) {
2418
+ actions.selectNode(state.focusedNodeId);
2419
+ } else {
2420
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
2421
+ }
1859
2422
  break;
1860
2423
  }
1861
2424
  case "Delete":
1862
2425
  case "Backspace": {
1863
2426
  e.preventDefault();
1864
- const toDelete = currentIndex >= 0 ? visibleNodes[currentIndex] : null;
1865
- if (toDelete && toDelete.parentId) {
1866
- const nextSelect = visibleNodes[currentIndex + 1] ?? visibleNodes[currentIndex - 1];
1867
- const newTree = (0, import_core4.removeNode)(state.tree, toDelete.id);
1868
- actions.setTree(newTree);
1869
- if (nextSelect && nextSelect.id !== toDelete.id) {
1870
- setFormSelectedNodeId(nextSelect.id);
1871
- } else {
1872
- setFormSelectedNodeId(null);
1873
- }
2427
+ const { newTree, nextFocusId } = deleteSelectedNodes(
2428
+ state.tree,
2429
+ state.selectedNodeIds,
2430
+ visibleNodes
2431
+ );
2432
+ if (newTree === state.tree) break;
2433
+ actions.setTree(newTree);
2434
+ if (nextFocusId) {
2435
+ actions.selectNode(nextFocusId);
2436
+ } else {
2437
+ actions.setSelection(null, /* @__PURE__ */ new Set(), null);
1874
2438
  }
1875
2439
  break;
1876
2440
  }
@@ -1878,7 +2442,8 @@ function FormView({
1878
2442
  },
1879
2443
  [
1880
2444
  visibleNodes,
1881
- formSelectedNodeId,
2445
+ state.focusedNodeId,
2446
+ state.selectedNodeIds,
1882
2447
  editingNodeId,
1883
2448
  collapsedIds,
1884
2449
  scrollToNode,
@@ -1886,7 +2451,7 @@ function FormView({
1886
2451
  actions
1887
2452
  ]
1888
2453
  );
1889
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2454
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1890
2455
  "div",
1891
2456
  {
1892
2457
  className,
@@ -1899,19 +2464,21 @@ function FormView({
1899
2464
  flexDirection: "column"
1900
2465
  },
1901
2466
  children: [
1902
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2467
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1903
2468
  "div",
1904
2469
  {
1905
2470
  style: {
2471
+ display: "flex",
2472
+ alignItems: "center",
1906
2473
  padding: "4px 8px",
1907
2474
  borderBottom: "1px solid var(--vj-border, #333333)",
1908
- backgroundColor: "var(--vj-bg-panel, #252526)",
2475
+ backgroundColor: "var(--vj-bg, #1e1e1e)",
1909
2476
  flexShrink: 0
1910
2477
  },
1911
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Breadcrumbs, {})
2478
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Breadcrumbs, {})
1912
2479
  }
1913
2480
  ),
1914
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2481
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1915
2482
  "div",
1916
2483
  {
1917
2484
  ref: containerRef,
@@ -1929,7 +2496,7 @@ function FormView({
1929
2496
  overflow: "auto",
1930
2497
  outline: "none"
1931
2498
  },
1932
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2499
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1933
2500
  FormField,
1934
2501
  {
1935
2502
  node: displayNode,
@@ -1938,7 +2505,6 @@ function FormView({
1938
2505
  depth: 0,
1939
2506
  showDescriptions,
1940
2507
  showCounts,
1941
- formSelectedNodeId,
1942
2508
  editingNodeId,
1943
2509
  collapsedIds,
1944
2510
  maxKeyLength,
@@ -1962,12 +2528,12 @@ function FormView({
1962
2528
  }
1963
2529
 
1964
2530
  // src/search-bar.tsx
1965
- var import_react8 = require("react");
1966
- var import_jsx_runtime6 = require("react/jsx-runtime");
2531
+ var import_react9 = require("react");
2532
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1967
2533
  function SearchBar({ className }) {
1968
2534
  const { state, actions } = useStudio();
1969
- const inputRef = (0, import_react8.useRef)(null);
1970
- const handleKeyDown = (0, import_react8.useCallback)(
2535
+ const inputRef = (0, import_react9.useRef)(null);
2536
+ const handleKeyDown = (0, import_react9.useCallback)(
1971
2537
  (e) => {
1972
2538
  if (e.key === "Enter") {
1973
2539
  e.preventDefault();
@@ -1985,7 +2551,7 @@ function SearchBar({ className }) {
1985
2551
  },
1986
2552
  [actions]
1987
2553
  );
1988
- (0, import_react8.useEffect)(() => {
2554
+ (0, import_react9.useEffect)(() => {
1989
2555
  function handleGlobalKeyDown(e) {
1990
2556
  const mod = e.metaKey || e.ctrlKey;
1991
2557
  if (mod && e.key === "f") {
@@ -1999,7 +2565,7 @@ function SearchBar({ className }) {
1999
2565
  }, []);
2000
2566
  const matchCount = state.searchMatches.length;
2001
2567
  const currentMatch = matchCount > 0 ? state.searchMatchIndex + 1 : 0;
2002
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2568
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2003
2569
  "div",
2004
2570
  {
2005
2571
  className,
@@ -2008,11 +2574,11 @@ function SearchBar({ className }) {
2008
2574
  alignItems: "center",
2009
2575
  gap: 6,
2010
2576
  padding: "4px 8px",
2011
- backgroundColor: "var(--vj-bg-panel, #252526)",
2577
+ backgroundColor: "var(--vj-bg, #1e1e1e)",
2012
2578
  borderBottom: "1px solid var(--vj-border, #333333)"
2013
2579
  },
2014
2580
  children: [
2015
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2581
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2016
2582
  "input",
2017
2583
  {
2018
2584
  ref: inputRef,
@@ -2028,14 +2594,14 @@ function SearchBar({ className }) {
2028
2594
  borderRadius: 3,
2029
2595
  color: "var(--vj-text, #cccccc)",
2030
2596
  fontFamily: "var(--vj-font, monospace)",
2031
- fontSize: 12,
2597
+ fontSize: "var(--vj-input-font-size, 13px)",
2032
2598
  padding: "3px 8px",
2033
2599
  outline: "none",
2034
2600
  minWidth: 0
2035
2601
  }
2036
2602
  }
2037
2603
  ),
2038
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2604
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2039
2605
  "div",
2040
2606
  {
2041
2607
  style: {
@@ -2045,8 +2611,8 @@ function SearchBar({ className }) {
2045
2611
  flexShrink: 0,
2046
2612
  height: 18
2047
2613
  },
2048
- children: state.searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2049
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2614
+ children: state.searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2615
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2050
2616
  "span",
2051
2617
  {
2052
2618
  style: {
@@ -2059,7 +2625,7 @@ function SearchBar({ className }) {
2059
2625
  children: matchCount > 0 ? `${currentMatch}/${matchCount}` : "0/0"
2060
2626
  }
2061
2627
  ),
2062
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2628
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2063
2629
  "button",
2064
2630
  {
2065
2631
  onClick: actions.prevSearchMatch,
@@ -2083,7 +2649,7 @@ function SearchBar({ className }) {
2083
2649
  children: "\u25B2"
2084
2650
  }
2085
2651
  ),
2086
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2652
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2087
2653
  "button",
2088
2654
  {
2089
2655
  onClick: actions.nextSearchMatch,
@@ -2107,7 +2673,7 @@ function SearchBar({ className }) {
2107
2673
  children: "\u25BC"
2108
2674
  }
2109
2675
  ),
2110
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2676
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2111
2677
  "button",
2112
2678
  {
2113
2679
  onClick: () => actions.setSearchQuery(""),
@@ -2130,8 +2696,8 @@ function SearchBar({ className }) {
2130
2696
  children: "\xD7"
2131
2697
  }
2132
2698
  )
2133
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2134
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2699
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2700
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2135
2701
  "button",
2136
2702
  {
2137
2703
  onClick: actions.expandAll,
@@ -2148,7 +2714,7 @@ function SearchBar({ className }) {
2148
2714
  alignItems: "center"
2149
2715
  },
2150
2716
  title: "Expand all",
2151
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2717
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2152
2718
  "svg",
2153
2719
  {
2154
2720
  width: "14",
@@ -2160,15 +2726,15 @@ function SearchBar({ className }) {
2160
2726
  strokeLinecap: "round",
2161
2727
  strokeLinejoin: "round",
2162
2728
  children: [
2163
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 2v4M5 4l3-2 3 2" }),
2164
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 14v-4M5 12l3 2 3-2" }),
2165
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M2 8h12" })
2729
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 2v4M5 4l3-2 3 2" }),
2730
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 14v-4M5 12l3 2 3-2" }),
2731
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M2 8h12" })
2166
2732
  ]
2167
2733
  }
2168
2734
  )
2169
2735
  }
2170
2736
  ),
2171
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2737
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2172
2738
  "button",
2173
2739
  {
2174
2740
  onClick: actions.collapseAll,
@@ -2185,7 +2751,7 @@ function SearchBar({ className }) {
2185
2751
  alignItems: "center"
2186
2752
  },
2187
2753
  title: "Collapse all",
2188
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2754
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2189
2755
  "svg",
2190
2756
  {
2191
2757
  width: "14",
@@ -2197,9 +2763,9 @@ function SearchBar({ className }) {
2197
2763
  strokeLinecap: "round",
2198
2764
  strokeLinejoin: "round",
2199
2765
  children: [
2200
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 5V1M5 3l3 2 3-2" }),
2201
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 11v4M5 13l3-2 3 2" }),
2202
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M2 8h12" })
2766
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 5V1M5 3l3 2 3-2" }),
2767
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 11v4M5 13l3-2 3 2" }),
2768
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M2 8h12" })
2203
2769
  ]
2204
2770
  }
2205
2771
  )
@@ -2214,31 +2780,7 @@ function SearchBar({ className }) {
2214
2780
  }
2215
2781
 
2216
2782
  // src/json-editor.tsx
2217
- var import_jsx_runtime7 = require("react/jsx-runtime");
2218
- var DEFAULT_CSS_VARS = {
2219
- "--vj-bg": "#1e1e1e",
2220
- "--vj-bg-panel": "#252526",
2221
- "--vj-bg-hover": "#2a2d2e",
2222
- "--vj-bg-selected": "#2a5a1e",
2223
- "--vj-bg-selected-muted": "#2a2d2e",
2224
- "--vj-bg-match": "#3a3520",
2225
- "--vj-bg-match-active": "#51502b",
2226
- "--vj-border": "#333333",
2227
- "--vj-border-subtle": "#2a2a2a",
2228
- "--vj-text": "#cccccc",
2229
- "--vj-text-muted": "#888888",
2230
- "--vj-text-dim": "#666666",
2231
- "--vj-text-dimmer": "#555555",
2232
- "--vj-string": "#ce9178",
2233
- "--vj-number": "#b5cea8",
2234
- "--vj-boolean": "#569cd6",
2235
- "--vj-accent": "#007acc",
2236
- "--vj-accent-muted": "#094771",
2237
- "--vj-input-bg": "#3c3c3c",
2238
- "--vj-input-border": "#555555",
2239
- "--vj-error": "#f48771",
2240
- "--vj-font": "monospace"
2241
- };
2783
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2242
2784
  function JsonEditor({
2243
2785
  value,
2244
2786
  defaultValue,
@@ -2257,15 +2799,15 @@ function JsonEditor({
2257
2799
  }) {
2258
2800
  const isControlled = value !== void 0;
2259
2801
  const initialValue = isControlled ? value : defaultValue ?? {};
2260
- const [editorKey, setEditorKey] = (0, import_react9.useState)(0);
2261
- const valueRef = (0, import_react9.useRef)(initialValue);
2262
- (0, import_react9.useEffect)(() => {
2802
+ const [editorKey, setEditorKey] = (0, import_react10.useState)(0);
2803
+ const valueRef = (0, import_react10.useRef)(initialValue);
2804
+ (0, import_react10.useEffect)(() => {
2263
2805
  if (isControlled && value !== valueRef.current) {
2264
2806
  valueRef.current = value;
2265
2807
  setEditorKey((k) => k + 1);
2266
2808
  }
2267
2809
  }, [value, isControlled]);
2268
- const handleChange = (0, import_react9.useCallback)(
2810
+ const handleChange = (0, import_react10.useCallback)(
2269
2811
  (newValue) => {
2270
2812
  valueRef.current = newValue;
2271
2813
  if (!readOnly) {
@@ -2283,25 +2825,35 @@ function JsonEditor({
2283
2825
  ...DEFAULT_CSS_VARS,
2284
2826
  ...style
2285
2827
  };
2286
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className, style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2287
- VisualJson,
2288
- {
2289
- value: valueRef.current,
2290
- onChange: readOnly ? void 0 : handleChange,
2291
- schema,
2292
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2293
- EditorLayout,
2294
- {
2295
- treeShowValues,
2296
- treeShowCounts,
2297
- editorShowDescriptions,
2298
- editorShowCounts,
2299
- sidebarOpen
2828
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className, "data-vj-root": "", style: containerStyle, children: [
2829
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2830
+ "style",
2831
+ {
2832
+ dangerouslySetInnerHTML: {
2833
+ __html: `@media(pointer:coarse){[data-vj-root]{--vj-input-font-size:16px}}`
2300
2834
  }
2301
- )
2302
- },
2303
- editorKey
2304
- ) });
2835
+ }
2836
+ ),
2837
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2838
+ VisualJson,
2839
+ {
2840
+ value: valueRef.current,
2841
+ onChange: readOnly ? void 0 : handleChange,
2842
+ schema,
2843
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2844
+ EditorLayout,
2845
+ {
2846
+ treeShowValues,
2847
+ treeShowCounts,
2848
+ editorShowDescriptions,
2849
+ editorShowCounts,
2850
+ sidebarOpen
2851
+ }
2852
+ )
2853
+ },
2854
+ editorKey
2855
+ )
2856
+ ] });
2305
2857
  }
2306
2858
  function EditorLayout({
2307
2859
  treeShowValues,
@@ -2310,14 +2862,14 @@ function EditorLayout({
2310
2862
  editorShowCounts,
2311
2863
  sidebarOpen
2312
2864
  }) {
2313
- const [sidebarWidth, setSidebarWidth] = (0, import_react9.useState)(280);
2314
- const [isNarrow, setIsNarrow] = (0, import_react9.useState)(false);
2315
- const [activePanel, setActivePanel] = (0, import_react9.useState)("tree");
2316
- const containerRef = (0, import_react9.useRef)(null);
2317
- const dragging = (0, import_react9.useRef)(false);
2318
- const startX = (0, import_react9.useRef)(0);
2319
- const startWidth = (0, import_react9.useRef)(0);
2320
- (0, import_react9.useEffect)(() => {
2865
+ const [sidebarWidth, setSidebarWidth] = (0, import_react10.useState)(280);
2866
+ const [isNarrow, setIsNarrow] = (0, import_react10.useState)(false);
2867
+ const [activePanel, setActivePanel] = (0, import_react10.useState)("tree");
2868
+ const containerRef = (0, import_react10.useRef)(null);
2869
+ const dragging = (0, import_react10.useRef)(false);
2870
+ const startX = (0, import_react10.useRef)(0);
2871
+ const startWidth = (0, import_react10.useRef)(0);
2872
+ (0, import_react10.useEffect)(() => {
2321
2873
  function checkWidth() {
2322
2874
  if (containerRef.current) {
2323
2875
  setIsNarrow(containerRef.current.offsetWidth < 500);
@@ -2328,7 +2880,7 @@ function EditorLayout({
2328
2880
  if (containerRef.current) observer.observe(containerRef.current);
2329
2881
  return () => observer.disconnect();
2330
2882
  }, []);
2331
- const handleMouseDown = (0, import_react9.useCallback)(
2883
+ const handleMouseDown = (0, import_react10.useCallback)(
2332
2884
  (e) => {
2333
2885
  dragging.current = true;
2334
2886
  startX.current = e.clientX;
@@ -2358,7 +2910,7 @@ function EditorLayout({
2358
2910
  );
2359
2911
  if (isNarrow) {
2360
2912
  if (!sidebarOpen) {
2361
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2913
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2362
2914
  "div",
2363
2915
  {
2364
2916
  ref: containerRef,
@@ -2368,7 +2920,7 @@ function EditorLayout({
2368
2920
  flex: 1,
2369
2921
  minHeight: 0
2370
2922
  },
2371
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2923
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2372
2924
  FormView,
2373
2925
  {
2374
2926
  showDescriptions: editorShowDescriptions,
@@ -2378,7 +2930,7 @@ function EditorLayout({
2378
2930
  }
2379
2931
  );
2380
2932
  }
2381
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2933
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2382
2934
  "div",
2383
2935
  {
2384
2936
  ref: containerRef,
@@ -2389,7 +2941,7 @@ function EditorLayout({
2389
2941
  minHeight: 0
2390
2942
  },
2391
2943
  children: [
2392
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2944
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2393
2945
  "div",
2394
2946
  {
2395
2947
  style: {
@@ -2399,7 +2951,7 @@ function EditorLayout({
2399
2951
  backgroundColor: "var(--vj-bg-panel, #252526)"
2400
2952
  },
2401
2953
  children: [
2402
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2954
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2403
2955
  "button",
2404
2956
  {
2405
2957
  onClick: () => setActivePanel("tree"),
@@ -2416,7 +2968,7 @@ function EditorLayout({
2416
2968
  children: "Tree"
2417
2969
  }
2418
2970
  ),
2419
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2971
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2420
2972
  "button",
2421
2973
  {
2422
2974
  onClick: () => setActivePanel("form"),
@@ -2436,7 +2988,7 @@ function EditorLayout({
2436
2988
  ]
2437
2989
  }
2438
2990
  ),
2439
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minHeight: 0, overflow: "hidden" }, children: activePanel === "tree" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2991
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, minHeight: 0, overflow: "hidden" }, children: activePanel === "tree" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2440
2992
  "div",
2441
2993
  {
2442
2994
  style: {
@@ -2445,8 +2997,8 @@ function EditorLayout({
2445
2997
  height: "100%"
2446
2998
  },
2447
2999
  children: [
2448
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchBar, {}),
2449
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3000
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SearchBar, {}),
3001
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2450
3002
  TreeView,
2451
3003
  {
2452
3004
  showValues: treeShowValues,
@@ -2455,7 +3007,7 @@ function EditorLayout({
2455
3007
  ) })
2456
3008
  ]
2457
3009
  }
2458
- ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3010
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2459
3011
  "div",
2460
3012
  {
2461
3013
  style: {
@@ -2463,7 +3015,7 @@ function EditorLayout({
2463
3015
  flexDirection: "column",
2464
3016
  height: "100%"
2465
3017
  },
2466
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3018
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2467
3019
  FormView,
2468
3020
  {
2469
3021
  showDescriptions: editorShowDescriptions,
@@ -2476,8 +3028,8 @@ function EditorLayout({
2476
3028
  }
2477
3029
  );
2478
3030
  }
2479
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { ref: containerRef, style: { display: "flex", flex: 1, minHeight: 0 }, children: [
2480
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3031
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ref: containerRef, style: { display: "flex", flex: 1, minHeight: 0 }, children: [
3032
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2481
3033
  "div",
2482
3034
  {
2483
3035
  style: {
@@ -2489,12 +3041,12 @@ function EditorLayout({
2489
3041
  transition: "width 0.2s ease"
2490
3042
  },
2491
3043
  children: [
2492
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchBar, {}),
2493
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TreeView, { showValues: treeShowValues, showCounts: treeShowCounts }) })
3044
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SearchBar, {}),
3045
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TreeView, { showValues: treeShowValues, showCounts: treeShowCounts }) })
2494
3046
  ]
2495
3047
  }
2496
3048
  ),
2497
- sidebarOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3049
+ sidebarOpen && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2498
3050
  "div",
2499
3051
  {
2500
3052
  style: {
@@ -2504,7 +3056,7 @@ function EditorLayout({
2504
3056
  position: "relative",
2505
3057
  transition: "background-color 0.15s"
2506
3058
  },
2507
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3059
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2508
3060
  "div",
2509
3061
  {
2510
3062
  onMouseDown: handleMouseDown,
@@ -2533,7 +3085,7 @@ function EditorLayout({
2533
3085
  )
2534
3086
  }
2535
3087
  ),
2536
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3088
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2537
3089
  "div",
2538
3090
  {
2539
3091
  style: {
@@ -2543,7 +3095,7 @@ function EditorLayout({
2543
3095
  minWidth: 0,
2544
3096
  overflow: "hidden"
2545
3097
  },
2546
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3098
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2547
3099
  FormView,
2548
3100
  {
2549
3101
  showDescriptions: editorShowDescriptions,
@@ -2555,424 +3107,6 @@ function EditorLayout({
2555
3107
  ] });
2556
3108
  }
2557
3109
 
2558
- // src/property-editor.tsx
2559
- var import_react10 = require("react");
2560
- var import_core5 = require("@visual-json/core");
2561
- var import_jsx_runtime8 = require("react/jsx-runtime");
2562
- var ALL_TYPES = [
2563
- "string",
2564
- "number",
2565
- "boolean",
2566
- "null",
2567
- "object",
2568
- "array"
2569
- ];
2570
- function PropertyRow({ node, schemaProperty }) {
2571
- const { state, actions } = useStudio();
2572
- const isContainer = node.type === "object" || node.type === "array";
2573
- const [hoveredRow, setHoveredRow] = (0, import_react10.useState)(false);
2574
- function handleValueChange(newValue) {
2575
- let parsed;
2576
- if (newValue === "null") parsed = null;
2577
- else if (newValue === "true") parsed = true;
2578
- else if (newValue === "false") parsed = false;
2579
- else if ((node.type === "number" || schemaProperty?.type === "number" || schemaProperty?.type === "integer") && !isNaN(Number(newValue)) && newValue.trim() !== "")
2580
- parsed = Number(newValue);
2581
- else parsed = newValue;
2582
- const newTree = (0, import_core5.setValue)(state.tree, node.id, parsed);
2583
- actions.setTree(newTree);
2584
- }
2585
- function handleKeyChange(newKey) {
2586
- const newTree = (0, import_core5.setKey)(state.tree, node.id, newKey);
2587
- actions.setTree(newTree);
2588
- }
2589
- function handleRemove() {
2590
- const newTree = (0, import_core5.removeNode)(state.tree, node.id);
2591
- actions.setTree(newTree);
2592
- }
2593
- function handleAddChild() {
2594
- const key = node.type === "array" ? String(node.children.length) : `key${node.children.length}`;
2595
- const newTree = (0, import_core5.addProperty)(state.tree, node.id, key, "");
2596
- actions.setTree(newTree);
2597
- }
2598
- function displayValue() {
2599
- if (isContainer) {
2600
- return node.type === "array" ? `[${node.children.length} items]` : `{${node.children.length} keys}`;
2601
- }
2602
- if (node.value === null) return "";
2603
- if (node.value === void 0) return "";
2604
- if (typeof node.value === "string" && node.value === "") return "";
2605
- return String(node.value);
2606
- }
2607
- const hasEnumValues = schemaProperty?.enum && schemaProperty.enum.length > 0;
2608
- const description = schemaProperty?.description;
2609
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2610
- "div",
2611
- {
2612
- onMouseEnter: () => setHoveredRow(true),
2613
- onMouseLeave: () => setHoveredRow(false),
2614
- children: [
2615
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2616
- "div",
2617
- {
2618
- style: {
2619
- display: "flex",
2620
- alignItems: "center",
2621
- gap: 8,
2622
- padding: "4px 12px",
2623
- borderBottom: "1px solid var(--vj-border-subtle, #2a2a2a)",
2624
- minHeight: 32,
2625
- backgroundColor: hoveredRow ? "var(--vj-bg-hover, #2a2d2e)" : "transparent"
2626
- },
2627
- children: [
2628
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2629
- "input",
2630
- {
2631
- value: node.key,
2632
- onChange: (e) => handleKeyChange(e.target.value),
2633
- style: {
2634
- background: "none",
2635
- border: "1px solid transparent",
2636
- borderRadius: 3,
2637
- color: "var(--vj-text, #cccccc)",
2638
- fontFamily: "var(--vj-font, monospace)",
2639
- fontSize: 13,
2640
- padding: "2px 6px",
2641
- width: 140,
2642
- flexShrink: 0
2643
- }
2644
- }
2645
- ),
2646
- !isContainer ? hasEnumValues ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2647
- "select",
2648
- {
2649
- value: displayValue(),
2650
- onChange: (e) => handleValueChange(e.target.value),
2651
- style: {
2652
- background: "var(--vj-input-bg, #3c3c3c)",
2653
- border: "1px solid var(--vj-input-border, #555555)",
2654
- borderRadius: 3,
2655
- color: node.type === "string" ? "var(--vj-string, #ce9178)" : "var(--vj-number, #b5cea8)",
2656
- fontFamily: "var(--vj-font, monospace)",
2657
- fontSize: 13,
2658
- padding: "2px 6px",
2659
- flex: 1,
2660
- cursor: "pointer"
2661
- },
2662
- children: schemaProperty.enum.map((v, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: String(v), children: String(v) }, i))
2663
- }
2664
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2665
- "input",
2666
- {
2667
- value: displayValue(),
2668
- onChange: (e) => handleValueChange(e.target.value),
2669
- placeholder: "<value>",
2670
- style: {
2671
- background: "none",
2672
- border: "1px solid transparent",
2673
- borderRadius: 3,
2674
- color: node.type === "string" ? "var(--vj-string, #ce9178)" : "var(--vj-number, #b5cea8)",
2675
- fontFamily: "var(--vj-font, monospace)",
2676
- fontSize: 13,
2677
- padding: "2px 6px",
2678
- flex: 1,
2679
- textAlign: "right"
2680
- }
2681
- }
2682
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2683
- "span",
2684
- {
2685
- style: {
2686
- color: "var(--vj-text-dim, #666666)",
2687
- fontFamily: "var(--vj-font, monospace)",
2688
- fontSize: 13,
2689
- flex: 1,
2690
- textAlign: "right"
2691
- },
2692
- children: displayValue()
2693
- }
2694
- ),
2695
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2696
- "div",
2697
- {
2698
- style: {
2699
- display: "flex",
2700
- gap: 2,
2701
- opacity: hoveredRow ? 1 : 0,
2702
- transition: "opacity 0.1s",
2703
- flexShrink: 0
2704
- },
2705
- children: [
2706
- isContainer && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2707
- "button",
2708
- {
2709
- onClick: handleAddChild,
2710
- style: {
2711
- background: "none",
2712
- border: "none",
2713
- color: "var(--vj-text-muted, #888888)",
2714
- cursor: "pointer",
2715
- padding: "2px 4px",
2716
- fontSize: 15,
2717
- fontFamily: "var(--vj-font, monospace)",
2718
- borderRadius: 3,
2719
- lineHeight: 1
2720
- },
2721
- title: "Add child",
2722
- children: "+"
2723
- }
2724
- ),
2725
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2726
- "button",
2727
- {
2728
- onClick: handleRemove,
2729
- style: {
2730
- background: "none",
2731
- border: "none",
2732
- color: "var(--vj-text-muted, #888888)",
2733
- cursor: "pointer",
2734
- padding: "2px 4px",
2735
- fontSize: 15,
2736
- fontFamily: "var(--vj-font, monospace)",
2737
- borderRadius: 3,
2738
- lineHeight: 1
2739
- },
2740
- title: "Remove",
2741
- children: "\xD7"
2742
- }
2743
- )
2744
- ]
2745
- }
2746
- )
2747
- ]
2748
- }
2749
- ),
2750
- description && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2751
- "div",
2752
- {
2753
- style: {
2754
- padding: "2px 12px 4px 44px",
2755
- fontSize: 11,
2756
- color: "var(--vj-text-dim, #666666)",
2757
- fontFamily: "var(--vj-font, monospace)",
2758
- borderBottom: "1px solid var(--vj-border-subtle, #2a2a2a)"
2759
- },
2760
- children: description
2761
- }
2762
- )
2763
- ]
2764
- }
2765
- );
2766
- }
2767
- function PropertyEditor({ className }) {
2768
- const { state, actions } = useStudio();
2769
- const selectedNode = state.selectedNodeId ? state.tree.nodesById.get(state.selectedNodeId) : null;
2770
- const handleChangeType = (0, import_react10.useCallback)(
2771
- (newType) => {
2772
- if (!selectedNode) return;
2773
- const newTree = (0, import_core5.changeType)(state.tree, selectedNode.id, newType);
2774
- actions.setTree(newTree);
2775
- },
2776
- [state.tree, selectedNode, actions]
2777
- );
2778
- const handleDuplicate = (0, import_react10.useCallback)(() => {
2779
- if (!selectedNode) return;
2780
- const newTree = (0, import_core5.duplicateNode)(state.tree, selectedNode.id);
2781
- actions.setTree(newTree);
2782
- }, [state.tree, selectedNode, actions]);
2783
- const handleCopyPath = (0, import_react10.useCallback)(() => {
2784
- if (!selectedNode) return;
2785
- navigator.clipboard.writeText(selectedNode.path).catch(() => {
2786
- });
2787
- }, [selectedNode]);
2788
- const handleCopyValue = (0, import_react10.useCallback)(() => {
2789
- if (!selectedNode) return;
2790
- const value = (0, import_core5.toJson)(selectedNode);
2791
- const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
2792
- navigator.clipboard.writeText(text).catch(() => {
2793
- });
2794
- }, [selectedNode]);
2795
- if (!selectedNode) {
2796
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2797
- "div",
2798
- {
2799
- className,
2800
- style: {
2801
- display: "flex",
2802
- alignItems: "center",
2803
- justifyContent: "center",
2804
- backgroundColor: "var(--vj-bg, #1e1e1e)",
2805
- color: "var(--vj-text-dimmer, #555555)",
2806
- fontFamily: "var(--vj-font, monospace)",
2807
- fontSize: 13,
2808
- height: "100%"
2809
- },
2810
- children: "Select a node to edit"
2811
- }
2812
- );
2813
- }
2814
- const isContainer = selectedNode.type === "object" || selectedNode.type === "array";
2815
- const schema = state.schema ?? void 0;
2816
- const schemaTitle = schema?.title;
2817
- function getChildSchema(childKey) {
2818
- if (!schema || !selectedNode) return void 0;
2819
- const childPath = selectedNode.path === "/" ? `/${childKey}` : `${selectedNode.path}/${childKey}`;
2820
- const raw = (0, import_core5.getPropertySchema)(schema, childPath);
2821
- return raw ? (0, import_core5.resolveRef)(raw, schema) : void 0;
2822
- }
2823
- function handleAdd() {
2824
- if (!selectedNode) return;
2825
- const key = selectedNode.type === "array" ? String(selectedNode.children.length) : `key${selectedNode.children.length}`;
2826
- const newTree = (0, import_core5.addProperty)(state.tree, selectedNode.id, key, "");
2827
- actions.setTree(newTree);
2828
- }
2829
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2830
- "div",
2831
- {
2832
- className,
2833
- style: {
2834
- backgroundColor: "var(--vj-bg, #1e1e1e)",
2835
- color: "var(--vj-text, #cccccc)",
2836
- overflow: "auto",
2837
- height: "100%",
2838
- display: "flex",
2839
- flexDirection: "column"
2840
- },
2841
- children: [
2842
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2843
- "div",
2844
- {
2845
- style: {
2846
- display: "flex",
2847
- alignItems: "center",
2848
- justifyContent: "space-between",
2849
- padding: "6px 12px",
2850
- borderBottom: "1px solid var(--vj-border, #333333)",
2851
- fontSize: 12,
2852
- color: "var(--vj-text-muted, #999999)",
2853
- fontFamily: "var(--vj-font, monospace)",
2854
- flexShrink: 0,
2855
- backgroundColor: "var(--vj-bg-panel, #252526)"
2856
- },
2857
- children: [
2858
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2859
- "div",
2860
- {
2861
- style: {
2862
- display: "flex",
2863
- flexDirection: "column",
2864
- gap: 2,
2865
- flex: 1,
2866
- minWidth: 0
2867
- },
2868
- children: [
2869
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Breadcrumbs, {}),
2870
- schemaTitle && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2871
- "span",
2872
- {
2873
- style: { fontSize: 10, color: "var(--vj-text-dim, #666666)" },
2874
- children: schemaTitle
2875
- }
2876
- )
2877
- ]
2878
- }
2879
- ),
2880
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2881
- "div",
2882
- {
2883
- style: {
2884
- display: "flex",
2885
- alignItems: "center",
2886
- gap: 4,
2887
- flexShrink: 0
2888
- },
2889
- children: [
2890
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2891
- "select",
2892
- {
2893
- value: selectedNode.type,
2894
- onChange: (e) => handleChangeType(e.target.value),
2895
- style: {
2896
- background: "var(--vj-input-bg, #3c3c3c)",
2897
- border: "1px solid var(--vj-input-border, #555555)",
2898
- borderRadius: 3,
2899
- color: "var(--vj-text, #cccccc)",
2900
- fontSize: 11,
2901
- fontFamily: "var(--vj-font, monospace)",
2902
- padding: "1px 4px",
2903
- cursor: "pointer"
2904
- },
2905
- title: "Change type",
2906
- children: ALL_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: t, children: t }, t))
2907
- }
2908
- ),
2909
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2910
- "button",
2911
- {
2912
- onClick: handleCopyPath,
2913
- style: actionButtonStyle,
2914
- title: "Copy path",
2915
- children: "path"
2916
- }
2917
- ),
2918
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2919
- "button",
2920
- {
2921
- onClick: handleCopyValue,
2922
- style: actionButtonStyle,
2923
- title: "Copy value",
2924
- children: "value"
2925
- }
2926
- ),
2927
- selectedNode.parentId && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2928
- "button",
2929
- {
2930
- onClick: handleDuplicate,
2931
- style: actionButtonStyle,
2932
- title: "Duplicate",
2933
- children: "dup"
2934
- }
2935
- ),
2936
- isContainer && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2937
- "button",
2938
- {
2939
- onClick: handleAdd,
2940
- style: {
2941
- ...actionButtonStyle,
2942
- border: "1px solid var(--vj-input-border, #555555)"
2943
- },
2944
- children: "+ Add"
2945
- }
2946
- )
2947
- ]
2948
- }
2949
- )
2950
- ]
2951
- }
2952
- ),
2953
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, overflow: "auto" }, children: isContainer ? selectedNode.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2954
- PropertyRow,
2955
- {
2956
- node: child,
2957
- schemaProperty: getChildSchema(child.key)
2958
- },
2959
- child.id
2960
- )) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PropertyRow, { node: selectedNode }) })
2961
- ]
2962
- }
2963
- );
2964
- }
2965
- var actionButtonStyle = {
2966
- background: "none",
2967
- border: "none",
2968
- borderRadius: 3,
2969
- color: "var(--vj-text-muted, #888888)",
2970
- cursor: "pointer",
2971
- padding: "1px 6px",
2972
- fontSize: 11,
2973
- fontFamily: "var(--vj-font, monospace)"
2974
- };
2975
-
2976
3110
  // src/diff-view.tsx
2977
3111
  var import_react11 = require("react");
2978
3112
  var import_core6 = require("@visual-json/core");
@@ -3133,7 +3267,6 @@ function DiffView({
3133
3267
  DiffView,
3134
3268
  FormView,
3135
3269
  JsonEditor,
3136
- PropertyEditor,
3137
3270
  SearchBar,
3138
3271
  StudioContext,
3139
3272
  TreeView,