@@ -14,7 +14,8 @@ import { filterNode, isCollection } from './filterHelpers'
1414import './style.css'
1515import { AutogrowTextArea } from './AutogrowTextArea'
1616import { useTheme } from './theme'
17- import { useCollapseAll } from './CollapseProvider'
17+ import { useTreeState } from './TreeStateProvider'
18+ import { toPathString } from './ValueNodes'
1819
1920export const CollectionNode : React . FC < CollectionNodeProps > = ( {
2021 data,
@@ -24,7 +25,14 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
2425 ...props
2526} ) => {
2627 const { getStyles } = useTheme ( )
27- const { collapseState, setCollapseState, doesPathMatch } = useCollapseAll ( )
28+ const {
29+ collapseState,
30+ setCollapseState,
31+ doesPathMatch,
32+ currentlyEditingElement,
33+ setCurrentlyEditingElement,
34+ areChildrenBeingEdited,
35+ } = useTreeState ( )
2836 const {
2937 onEdit,
3038 onAdd,
@@ -43,8 +51,6 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
4351 translate,
4452 customNodeDefinitions,
4553 } = props
46- const [ isEditing , setIsEditing ] = useState ( false )
47- const [ isEditingKey , setIsEditingKey ] = useState ( false )
4854 const [ stringifiedValue , setStringifiedValue ] = useState ( JSON . stringify ( data , null , 2 ) )
4955 const [ error , setError ] = useState < string | null > ( null )
5056
@@ -54,6 +60,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
5460 const nodeData = { ...incomingNodeData , collapsed }
5561 const { path, key : name , size } = nodeData
5662
63+ const pathString = toPathString ( path )
64+
5765 // This allows us to not render the children on load if they're hidden (which
5866 // gives a big performance improvement with large data sets), but still keep
5967 // the animation transition when opening and closing the accordion
@@ -129,7 +137,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
129137 setCollapseState ( { open : ! collapsed , path } )
130138 return
131139 }
132- if ( ! isEditing ) {
140+ if ( ! ( currentlyEditingElement && currentlyEditingElement . includes ( pathString ) ) ) {
133141 setIsAnimating ( true )
134142 hasBeenOpened . current = true
135143 setCollapsed ( ! collapsed )
@@ -146,7 +154,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
146154 const handleEdit = ( ) => {
147155 try {
148156 const value = JSON5 . parse ( stringifiedValue )
149- setIsEditing ( false )
157+ setCurrentlyEditingElement ( null )
150158 setError ( null )
151159 if ( JSON . stringify ( value ) === JSON . stringify ( data ) ) return
152160 onEdit ( value , path ) . then ( ( error ) => {
@@ -160,7 +168,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
160168 }
161169
162170 const handleEditKey = ( newKey : string ) => {
163- setIsEditingKey ( false )
171+ setCurrentlyEditingElement ( null )
164172 if ( name === newKey ) return
165173 if ( ! parentData ) return
166174 const parentPath = path . slice ( 0 , - 1 )
@@ -204,30 +212,39 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
204212 : undefined
205213
206214 const handleCancel = ( ) => {
207- setIsEditing ( false )
208- setIsEditingKey ( false )
215+ setCurrentlyEditingElement ( null )
209216 setError ( null )
210217 setStringifiedValue ( JSON . stringify ( data , null , 2 ) )
211218 }
212219
220+ // DERIVED VALUES (this makes the render logic easier to understand)
221+ const isEditing = currentlyEditingElement === pathString
222+ const isEditingKey = currentlyEditingElement === `key_${ pathString } `
213223 const isArray = typeof path . slice ( - 1 ) [ 0 ] === 'number'
214224 const showLabel = showArrayIndices || ! isArray
215225 const showCount = showCollectionCount === 'when-closed' ? collapsed : showCollectionCount
226+ const showEditButtons = ! isEditing && showEditTools
227+ const showKey = showLabel && ! hideKey && name !== '' && name !== undefined
228+ const showCustomNodeContents =
229+ CustomNode && ( ( isEditing && showOnEdit ) || ( ! isEditing && showOnView ) )
230+ const sortKeys = keySort && collectionType === 'object'
216231
217232 const keyValueArray = Object . entries ( data ) . map ( ( [ key , value ] ) => [
218233 collectionType === 'array' ? Number ( key ) : key ,
219234 value ,
220235 ] )
221236
222- if ( keySort && collectionType === 'object' ) {
237+ if ( sortKeys ) {
223238 keyValueArray . sort (
224239 typeof keySort === 'function' ? ( a : string [ ] , b ) => keySort ( a [ 0 ] , b [ 0 ] as string ) : undefined
225240 )
226241 }
227242
228243 // A crude measure to estimate the approximate height of the block, for
229- // setting the max-height in the collapsible interior
230- const numOfLines = JSON . stringify ( data , null , 2 ) . split ( '\n' ) . length
244+ // setting the max-height in the collapsible interior.
245+ // The Regexp replacement is to parse escaped line breaks *within* the JSON
246+ // into *actual* line breaks before splitting
247+ const numOfLines = JSON . stringify ( data , null , 2 ) . replace ( / \\ n / g, '\n' ) . split ( '\n' ) . length
231248
232249 const CollectionChildren = ! hasBeenOpened . current ? null : ! isEditing ? (
233250 keyValueArray . map ( ( [ key , value ] , index ) => {
@@ -274,7 +291,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
274291 < div >
275292 < AutogrowTextArea
276293 className = "jer-collection-text-area"
277- name = { path . join ( '.' ) }
294+ name = { pathString }
278295 value = { stringifiedValue }
279296 setValue = { setStringifiedValue }
280297 isEditing = { isEditing }
@@ -306,24 +323,23 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
306323 handleCancel,
307324 handleKeyPress,
308325 isEditing,
309- setIsEditing,
326+ setIsEditing : ( ) => setCurrentlyEditingElement ( pathString ) ,
310327 getStyles,
311328 }
312329
313- const CollectionContents =
314- CustomNode && ( ( isEditing && showOnEdit ) || ( ! isEditing && showOnView ) ) ? (
315- < CustomNode customNodeProps = { customNodeProps } { ...customNodeAllProps } >
316- { CollectionChildren }
317- </ CustomNode >
318- ) : (
319- CollectionChildren
320- )
330+ const CollectionContents = showCustomNodeContents ? (
331+ < CustomNode customNodeProps = { customNodeProps } { ...customNodeAllProps } >
332+ { CollectionChildren }
333+ </ CustomNode >
334+ ) : (
335+ CollectionChildren
336+ )
321337
322338 const KeyDisplay = isEditingKey ? (
323339 < input
324340 className = "jer-collection-name"
325341 type = "text"
326- name = { path . join ( '.' ) }
342+ name = { pathString }
327343 defaultValue = { name }
328344 autoFocus
329345 onFocus = { ( e ) => e . target . select ( ) }
@@ -333,19 +349,19 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
333349 ) : (
334350 < span
335351 style = { getStyles ( 'property' , nodeData ) }
336- onDoubleClick = { ( ) => canEditKey && setIsEditingKey ( true ) }
352+ onDoubleClick = { ( ) => canEditKey && setCurrentlyEditingElement ( `key_ ${ pathString } ` ) }
337353 >
338- { showLabel && ! hideKey && name !== '' && name !== undefined ? `${ name } :` : null }
354+ { showKey ? `${ name } :` : null }
339355 </ span >
340356 )
341357
342- const EditButtonDisplay = ! isEditing && showEditTools && (
358+ const EditButtonDisplay = showEditButtons && (
343359 < EditButtons
344360 startEdit = {
345361 canEdit
346362 ? ( ) => {
347363 hasBeenOpened . current = true
348- setIsEditing ( true )
364+ setCurrentlyEditingElement ( pathString )
349365 setCollapsed ( false )
350366 }
351367 : undefined
@@ -412,7 +428,15 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
412428 < div
413429 className = { 'jer-collection-inner' }
414430 style = { {
415- maxHeight : isCollapsed ? 0 : ! isEditing ? `${ numOfLines * 3 } em` : undefined ,
431+ // Don't limit the height when collection or any of its children are
432+ // being edited, so it won't overlap lower elements if the editing
433+ // input gets too large. This won't cause problems, as it can't be
434+ // collapsed while being edited anyway.
435+ maxHeight : isCollapsed
436+ ? 0
437+ : ! areChildrenBeingEdited ( pathString )
438+ ? `${ numOfLines * 3 } em`
439+ : undefined ,
416440 overflowY : isCollapsed || isAnimating ? 'hidden' : 'visible' ,
417441 // Need to use max-height for animation to work, unfortunately
418442 // "height: auto" doesn't 😔
@@ -437,11 +461,11 @@ export const CollectionNode: React.FC<CollectionNodeProps> = ({
437461 </ div >
438462 )
439463
440- if ( CustomWrapper ) {
441- return (
442- < CustomWrapper customNodeProps = { wrapperProps } { ... customNodeAllProps } >
443- { CollectionNodeComponent }
444- </ CustomWrapper >
445- )
446- } else return CollectionNodeComponent
464+ return CustomWrapper ? (
465+ < CustomWrapper customNodeProps = { wrapperProps } { ... customNodeAllProps } >
466+ { CollectionNodeComponent }
467+ </ CustomWrapper >
468+ ) : (
469+ CollectionNodeComponent
470+ )
447471}
0 commit comments