@@ -2,9 +2,20 @@ import React, { useEffect, useState, useMemo, useRef } from 'react'
22import { ValueNodeWrapper } from './ValueNodeWrapper'
33import { EditButtons , InputButtons } from './ButtonPanels'
44import { getCustomNode } from './CustomNode'
5- import { type CollectionNodeProps , type NodeData , type CollectionData } from './types'
5+ import {
6+ type CollectionNodeProps ,
7+ type NodeData ,
8+ type CollectionData ,
9+ type ValueData ,
10+ } from './types'
611import { Icon } from './Icons'
7- import { filterNode , getModifier , isCollection } from './helpers'
12+ import {
13+ filterNode ,
14+ getModifier ,
15+ getNextOrPrevious ,
16+ insertCharInTextArea ,
17+ isCollection ,
18+ } from './helpers'
819import { AutogrowTextArea } from './AutogrowTextArea'
920import { useTheme , useTreeState } from './contexts'
1021import { useCollapseTransition , useCommon , useDragNDrop } from './hooks'
@@ -36,7 +47,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
3647 searchFilter,
3748 searchText,
3849 indent,
39- keySort ,
50+ sort ,
4051 showArrayIndices,
4152 defaultValue,
4253 translate,
@@ -101,6 +112,9 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
101112 }
102113 } , [ collapseState ] )
103114
115+ // For JSON-editing TextArea
116+ const textAreaRef = useRef < HTMLTextAreaElement > ( null )
117+
104118 const getDefaultNewValue = useMemo (
105119 ( ) => ( nodeData : NodeData , newKey : string ) => {
106120 if ( typeof defaultValue !== 'function' ) return defaultValue
@@ -121,19 +135,38 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
121135 showCollectionWrapper = true ,
122136 } = useMemo ( ( ) => getCustomNode ( customNodeDefinitions , nodeData ) , [ ] )
123137
138+ const childrenEditing = areChildrenBeingEdited ( pathString )
139+
140+ // For when children are accessed via Tab
141+ if ( childrenEditing && collapsed ) animateCollapse ( false )
142+
124143 // Early return if this node is filtered out
125- if ( ! filterNode ( 'collection' , nodeData , searchFilter , searchText ) && nodeData . level > 0 )
126- return null
144+ const isVisible =
145+ filterNode ( 'collection' , nodeData , searchFilter , searchText ) || nodeData . level === 0
146+ if ( ! isVisible && ! childrenEditing ) return null
127147
128148 const collectionType = Array . isArray ( data ) ? 'array' : 'object'
129149 const brackets =
130150 collectionType === 'array' ? { open : '[' , close : ']' } : { open : '{' , close : '}' }
131151
132- const handleKeyPressEdit = ( e : React . KeyboardEvent ) =>
152+ const handleKeyPressEdit = ( e : React . KeyboardEvent ) => {
153+ // Normal "Tab" key functionality in TextArea
154+ // Defined here explicitly rather than in handleKeyboard as we *don't* want
155+ // to override the normal Tab key with the custom "Tab" key value
156+ if ( e . key === 'Tab' && ! e . getModifierState ( 'Shift' ) ) {
157+ e . preventDefault ( )
158+ const newValue = insertCharInTextArea (
159+ textAreaRef as React . MutableRefObject < HTMLTextAreaElement > ,
160+ '\t'
161+ )
162+ setStringifiedValue ( newValue )
163+ return
164+ }
133165 handleKeyboard ( e , {
134166 objectConfirm : handleEdit ,
135167 cancel : handleCancel ,
136168 } )
169+ }
137170
138171 const handleCollapse = ( e : React . MouseEvent ) => {
139172 const modifier = getModifier ( e )
@@ -212,18 +245,13 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
212245 const showKey = showLabel && ! hideKey && name !== undefined
213246 const showCustomNodeContents =
214247 CustomNode && ( ( isEditing && showOnEdit ) || ( ! isEditing && showOnView ) )
215- const sortKeys = keySort && collectionType === 'object'
216248
217- const keyValueArray = Object . entries ( data ) . map ( ( [ key , value ] ) => [
218- collectionType === 'array' ? Number ( key ) : key ,
219- value ,
220- ] )
249+ const keyValueArray = Object . entries ( data ) . map (
250+ ( [ key , value ] ) =>
251+ [ collectionType === 'array' ? Number ( key ) : key , value ] as [ string | number , ValueData ]
252+ )
221253
222- if ( sortKeys ) {
223- keyValueArray . sort (
224- typeof keySort === 'function' ? ( a : string [ ] , b ) => keySort ( a [ 0 ] , b [ 0 ] as string ) : undefined
225- )
226- }
254+ if ( collectionType === 'object' ) sort < [ string | number , ValueData ] > ( keyValueArray , ( _ ) => _ )
227255
228256 const CollectionChildren = ! hasBeenOpened . current ? null : ! isEditing ? (
229257 keyValueArray . map ( ( [ key , value ] , index ) => {
@@ -271,6 +299,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
271299 < div className = "jer-collection-text-edit" >
272300 < div >
273301 < AutogrowTextArea
302+ textAreaRef = { textAreaRef }
274303 className = "jer-collection-text-area"
275304 name = { pathString }
276305 value = { stringifiedValue }
@@ -290,7 +319,9 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
290319 // no way to open a collapsed custom node, so this ensures it will stay open.
291320 // It can still be displayed collapsed by handling it internally if this is
292321 // desired.
293- const isCollapsed = ! showCollectionWrapper ? false : collapsed
322+ // Also, if the node is editing via "Tab" key, it's parent must be opened,
323+ // hence `childrenEditing` check
324+ const isCollapsed = ! showCollectionWrapper ? false : collapsed && ! childrenEditing
294325 if ( ! isCollapsed ) hasBeenOpened . current = true
295326
296327 const customNodeAllProps = {
@@ -304,7 +335,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
304335 handleCancel,
305336 handleKeyPress : handleKeyPressEdit ,
306337 isEditing,
307- setIsEditing : ( ) => setCurrentlyEditingElement ( pathString ) ,
338+ setIsEditing : ( ) => setCurrentlyEditingElement ( path ) ,
308339 getStyles,
309340 canDragOnto : canEdit ,
310341 }
@@ -329,6 +360,19 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
329360 handleKeyboard ( e , {
330361 stringConfirm : ( ) => handleEditKey ( ( e . target as HTMLInputElement ) . value ) ,
331362 cancel : handleCancel ,
363+ tabForward : ( ) => {
364+ handleEditKey ( ( e . target as HTMLInputElement ) . value )
365+ const firstChildKey = keyValueArray ?. [ 0 ] [ 0 ]
366+ setCurrentlyEditingElement (
367+ firstChildKey
368+ ? [ ...path , firstChildKey ]
369+ : getNextOrPrevious ( nodeData . fullData , path , 'next' , sort )
370+ )
371+ } ,
372+ tabBack : ( ) => {
373+ handleEditKey ( ( e . target as HTMLInputElement ) . value )
374+ setCurrentlyEditingElement ( getNextOrPrevious ( nodeData . fullData , path , 'prev' , sort ) )
375+ } ,
332376 } )
333377 }
334378 style = { { width : `${ String ( name ) . length / 1.5 + 0.5 } em` } }
@@ -339,7 +383,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
339383 className = "jer-key-text"
340384 style = { getStyles ( 'property' , nodeData ) }
341385 onClick = { ( e ) => e . stopPropagation ( ) }
342- onDoubleClick = { ( ) => canEditKey && setCurrentlyEditingElement ( `key_ ${ pathString } ` ) }
386+ onDoubleClick = { ( ) => canEditKey && setCurrentlyEditingElement ( path , 'key' ) }
343387 >
344388 { name === '' ? (
345389 < span className = { path . length > 0 ? 'jer-empty-string' : undefined } >
@@ -358,7 +402,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
358402 canEdit
359403 ? ( ) => {
360404 hasBeenOpened . current = true
361- setCurrentlyEditingElement ( pathString )
405+ setCurrentlyEditingElement ( path )
362406 }
363407 : undefined
364408 }
@@ -451,7 +495,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
451495 style = { {
452496 overflowY : isCollapsed || isAnimating ? 'clip' : 'visible' ,
453497 // Prevent collapse if this node or any children are being edited
454- maxHeight : areChildrenBeingEdited ( pathString ) ? undefined : maxHeight ,
498+ maxHeight : childrenEditing ? undefined : maxHeight ,
455499 ...getStyles ( 'collectionInner' , nodeData ) ,
456500 } }
457501 ref = { contentRef }
0 commit comments