@@ -371,9 +371,7 @@ const validateJson = () => {
371371 }
372372};
373373
374- const buildTreeNodes = (data : any , path : string [] = [], level : number = 0 ): JsonNodeType [] => {
375- const nodes: JsonNodeType [] = [];
376-
374+ const buildTreeNodes = (data : any , path : string [] = [], level : number = 0 , preserveState ? : JsonNodeType []): JsonNodeType [] => {
377375 if (data === null ) {
378376 return [{
379377 key: " " ,
@@ -384,40 +382,85 @@ const buildTreeNodes = (data: any, path: string[] = [], level: number = 0): Json
384382 }];
385383 }
386384
385+ // For root level objects and arrays, create a single container node
386+ if (level === 0 && (Array .isArray (data ) || (typeof data === " object" && data !== null ))) {
387+ const rootType = Array .isArray (data ) ? " array" : " object" ;
388+
389+ // Try to preserve expanded state from existing root node
390+ const existingRoot = preserveState && preserveState [0 ];
391+ const shouldExpand = existingRoot ? existingRoot .expanded : true ;
392+
393+ const rootNode: JsonNodeType = {
394+ key: rootType === " array" ? " Array" : " Object" ,
395+ value: data ,
396+ type: rootType ,
397+ path: [],
398+ level: 0 ,
399+ expanded: shouldExpand ,
400+ children: buildTreeNodesInternal (data , [], 1 , existingRoot ?.children )
401+ };
402+ return [rootNode ];
403+ }
404+
405+ return buildTreeNodesInternal (data , path , level , preserveState );
406+ };
407+
408+ const buildTreeNodesInternal = (data : any , path : string [] = [], level : number = 0 , preserveState ? : JsonNodeType []): JsonNodeType [] => {
409+ const nodes: JsonNodeType [] = [];
410+
411+ // Helper function to find existing node by path
412+ const findExistingNode = (existingNodes : JsonNodeType [] | undefined , targetPath : string []): JsonNodeType | undefined => {
413+ if (! existingNodes ) return undefined ;
414+ return existingNodes .find (node =>
415+ node .path .length === targetPath .length &&
416+ node .path .every ((segment , index ) => segment === targetPath [index ])
417+ );
418+ };
419+
387420 if (Array .isArray (data )) {
388421 data .forEach ((item , index ) => {
389422 const itemPath = [... path , index .toString ()];
390423 const itemType = getValueType (item );
424+
425+ // Try to find existing node to preserve expanded state
426+ const existingNode = findExistingNode (preserveState , itemPath );
427+ const shouldExpand = existingNode ? existingNode .expanded : level < props .maxDepth ;
428+
391429 const node: JsonNodeType = {
392430 key: index .toString (),
393431 value: item ,
394432 type: itemType ,
395433 path: itemPath ,
396434 level ,
397- expanded: level < props . maxDepth ,
435+ expanded: shouldExpand ,
398436 };
399437
400438 if (itemType === " object" || itemType === " array" ) {
401- node .children = buildTreeNodes (item , itemPath , level + 1 );
439+ node .children = buildTreeNodesInternal (item , itemPath , level + 1 , existingNode ?. children );
402440 }
403441
404442 nodes .push (node );
405443 });
406- } else if (typeof data === " object" ) {
444+ } else if (typeof data === " object" && data !== null ) {
407445 Object .entries (data ).forEach (([key , value ]) => {
408446 const itemPath = [... path , key ];
409447 const itemType = getValueType (value );
448+
449+ // Try to find existing node to preserve expanded state
450+ const existingNode = findExistingNode (preserveState , itemPath );
451+ const shouldExpand = existingNode ? existingNode .expanded : level < props .maxDepth ;
452+
410453 const node: JsonNodeType = {
411454 key ,
412455 value ,
413456 type: itemType ,
414457 path: itemPath ,
415458 level ,
416- expanded: level < props . maxDepth ,
459+ expanded: shouldExpand ,
417460 };
418461
419462 if (itemType === " object" || itemType === " array" ) {
420- node .children = buildTreeNodes (value , itemPath , level + 1 );
463+ node .children = buildTreeNodesInternal (value , itemPath , level + 1 , existingNode ?. children );
421464 }
422465
423466 nodes .push (node );
@@ -478,15 +521,31 @@ const handleKeyChange = (event: { node: JsonNodeType; oldKey: string; newKey: st
478521 const parentPath = event .node .path .slice (0 , - 1 );
479522 const parent = getNestedValue (newData , parentPath );
480523
481- if (parent && typeof parent === ' object' ) {
482- // Store the value
483- const value = parent [event .oldKey ];
484- // Delete the old key
485- delete parent [event .oldKey ];
486- // Add the new key with the same value
487- parent [event .newKey ] = value ;
488- // Update the path in the node
489- event .node .path [event .node .path .length - 1 ] = event .newKey ;
524+ if (parent && typeof parent === ' object' && ! Array .isArray (parent )) {
525+ // Get all keys in their current order
526+ const keys = Object .keys (parent );
527+ const values = keys .map (key => parent [key ]);
528+
529+ // Find the index of the old key
530+ const keyIndex = keys .indexOf (event .oldKey );
531+
532+ if (keyIndex !== - 1 ) {
533+ // Replace the old key with the new key at the same position
534+ keys [keyIndex ] = event .newKey ;
535+
536+ // Clear the parent object
537+ for (const key of Object .keys (parent )) {
538+ delete parent [key ];
539+ }
540+
541+ // Rebuild the object with keys in the same order
542+ keys .forEach ((key , index ) => {
543+ parent [key ] = values [index ];
544+ });
545+
546+ // Update the path in the node
547+ event .node .path [event .node .path .length - 1 ] = event .newKey ;
548+ }
490549 }
491550 emit (" update:data" , newData );
492551};
@@ -533,7 +592,8 @@ const getNestedValue = (obj: any, path: string[]) => {
533592};
534593
535594watch (() => props .data , (newData ) => {
536- rootNodes .value = buildTreeNodes (newData );
595+ // Preserve current expanded state when rebuilding tree
596+ rootNodes .value = buildTreeNodes (newData , [], 0 , rootNodes .value );
537597 jsonText .value = JSON .stringify (newData , null , 2 );
538598}, { deep: true , immediate: true });
539599
@@ -544,18 +604,6 @@ onMounted(() => {
544604 </script >
545605
546606<style scoped>
547- .json-viewer {
548- display : flex ;
549- flex-direction : column ;
550- height : 100% ;
551- font-family : ' Inter' , ' Roboto' , system-ui , sans-serif ;
552- font-size : 14px ;
553- line-height : 1.5 ;
554- border-radius : 8px ;
555- overflow : hidden ;
556- box-shadow : 0 4px 6px -1px rgba (0 , 0 , 0 , 0.1 );
557- }
558-
559607.json-viewer {
560608 /* Base styles */
561609 display : flex ;
@@ -720,14 +768,6 @@ onMounted(() => {
720768 transition : all 0.3s ease ;
721769}
722770
723- .theme-btn :hover .theme-icon {
724- transform : scale (1.1 );
725- }
726-
727- .theme-btn {
728- transition : all 0.2s ease ;
729- }
730-
731771/* Ensure SVG icons inherit the theme icon styling */
732772.theme-icon svg {
733773 transition : all 0.3s ease ;
@@ -1027,11 +1067,6 @@ onMounted(() => {
10271067 }
10281068}
10291069
1030- /* Hide hidden sections completely */
1031- .menu-bar.hidden {
1032- display : none !important ;
1033- }
1034-
10351070@media (max-width : 768px ) {
10361071 .menu-bar {
10371072 flex-direction : row ;
0 commit comments