@@ -2,9 +2,20 @@ import React, { useEffect, useState, useMemo, useRef } from 'react'
2
2
import { ValueNodeWrapper } from './ValueNodeWrapper'
3
3
import { EditButtons , InputButtons } from './ButtonPanels'
4
4
import { 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'
6
11
import { 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'
8
19
import { AutogrowTextArea } from './AutogrowTextArea'
9
20
import { useTheme , useTreeState } from './contexts'
10
21
import { useCollapseTransition , useCommon , useDragNDrop } from './hooks'
@@ -36,7 +47,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
36
47
searchFilter,
37
48
searchText,
38
49
indent,
39
- keySort ,
50
+ sort ,
40
51
showArrayIndices,
41
52
defaultValue,
42
53
translate,
@@ -101,6 +112,9 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
101
112
}
102
113
} , [ collapseState ] )
103
114
115
+ // For JSON-editing TextArea
116
+ const textAreaRef = useRef < HTMLTextAreaElement > ( null )
117
+
104
118
const getDefaultNewValue = useMemo (
105
119
( ) => ( nodeData : NodeData , newKey : string ) => {
106
120
if ( typeof defaultValue !== 'function' ) return defaultValue
@@ -121,19 +135,38 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
121
135
showCollectionWrapper = true ,
122
136
} = useMemo ( ( ) => getCustomNode ( customNodeDefinitions , nodeData ) , [ ] )
123
137
138
+ const childrenEditing = areChildrenBeingEdited ( pathString )
139
+
140
+ // For when children are accessed via Tab
141
+ if ( childrenEditing && collapsed ) animateCollapse ( false )
142
+
124
143
// 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
127
147
128
148
const collectionType = Array . isArray ( data ) ? 'array' : 'object'
129
149
const brackets =
130
150
collectionType === 'array' ? { open : '[' , close : ']' } : { open : '{' , close : '}' }
131
151
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
+ }
133
165
handleKeyboard ( e , {
134
166
objectConfirm : handleEdit ,
135
167
cancel : handleCancel ,
136
168
} )
169
+ }
137
170
138
171
const handleCollapse = ( e : React . MouseEvent ) => {
139
172
const modifier = getModifier ( e )
@@ -212,18 +245,13 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
212
245
const showKey = showLabel && ! hideKey && name !== undefined
213
246
const showCustomNodeContents =
214
247
CustomNode && ( ( isEditing && showOnEdit ) || ( ! isEditing && showOnView ) )
215
- const sortKeys = keySort && collectionType === 'object'
216
248
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
+ )
221
253
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 , ( _ ) => _ )
227
255
228
256
const CollectionChildren = ! hasBeenOpened . current ? null : ! isEditing ? (
229
257
keyValueArray . map ( ( [ key , value ] , index ) => {
@@ -271,6 +299,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
271
299
< div className = "jer-collection-text-edit" >
272
300
< div >
273
301
< AutogrowTextArea
302
+ textAreaRef = { textAreaRef }
274
303
className = "jer-collection-text-area"
275
304
name = { pathString }
276
305
value = { stringifiedValue }
@@ -290,7 +319,9 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
290
319
// no way to open a collapsed custom node, so this ensures it will stay open.
291
320
// It can still be displayed collapsed by handling it internally if this is
292
321
// 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
294
325
if ( ! isCollapsed ) hasBeenOpened . current = true
295
326
296
327
const customNodeAllProps = {
@@ -304,7 +335,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
304
335
handleCancel,
305
336
handleKeyPress : handleKeyPressEdit ,
306
337
isEditing,
307
- setIsEditing : ( ) => setCurrentlyEditingElement ( pathString ) ,
338
+ setIsEditing : ( ) => setCurrentlyEditingElement ( path ) ,
308
339
getStyles,
309
340
canDragOnto : canEdit ,
310
341
}
@@ -329,6 +360,19 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
329
360
handleKeyboard ( e , {
330
361
stringConfirm : ( ) => handleEditKey ( ( e . target as HTMLInputElement ) . value ) ,
331
362
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
+ } ,
332
376
} )
333
377
}
334
378
style = { { width : `${ String ( name ) . length / 1.5 + 0.5 } em` } }
@@ -339,7 +383,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
339
383
className = "jer-key-text"
340
384
style = { getStyles ( 'property' , nodeData ) }
341
385
onClick = { ( e ) => e . stopPropagation ( ) }
342
- onDoubleClick = { ( ) => canEditKey && setCurrentlyEditingElement ( `key_ ${ pathString } ` ) }
386
+ onDoubleClick = { ( ) => canEditKey && setCurrentlyEditingElement ( path , 'key' ) }
343
387
>
344
388
{ name === '' ? (
345
389
< span className = { path . length > 0 ? 'jer-empty-string' : undefined } >
@@ -358,7 +402,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
358
402
canEdit
359
403
? ( ) => {
360
404
hasBeenOpened . current = true
361
- setCurrentlyEditingElement ( pathString )
405
+ setCurrentlyEditingElement ( path )
362
406
}
363
407
: undefined
364
408
}
@@ -451,7 +495,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
451
495
style = { {
452
496
overflowY : isCollapsed || isAnimating ? 'clip' : 'visible' ,
453
497
// Prevent collapse if this node or any children are being edited
454
- maxHeight : areChildrenBeingEdited ( pathString ) ? undefined : maxHeight ,
498
+ maxHeight : childrenEditing ? undefined : maxHeight ,
455
499
...getStyles ( 'collectionInner' , nodeData ) ,
456
500
} }
457
501
ref = { contentRef }
0 commit comments