Skip to content

Commit ae42a77

Browse files
committed
Abstract KeyDisplay into single component
1 parent fa83931 commit ae42a77

File tree

4 files changed

+129
-100
lines changed

4 files changed

+129
-100
lines changed

demo/src/_imports.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
*/
44

55
/* Installed package */
6-
export * from 'json-edit-react'
6+
// export * from 'json-edit-react'
77

88
/* Local src */
9-
// export * from './json-edit-react/src'
9+
export * from './json-edit-react/src'
1010

1111
/* Compiled local package */
1212
// export * from './package/build'

src/CollectionNode.tsx

+18-50
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
isCollection,
1818
} from './helpers'
1919
import { AutogrowTextArea } from './AutogrowTextArea'
20+
import { KeyDisplay } from './KeyDisplay'
2021
import { useTheme, useTreeState } from './contexts'
2122
import { useCollapseTransition, useCommon, useDragNDrop } from './hooks'
2223

@@ -359,54 +360,6 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
359360
CollectionChildren
360361
)
361362

362-
const KeyDisplay = isEditingKey ? (
363-
<input
364-
className="jer-input-text jer-key-edit"
365-
type="text"
366-
name={pathString}
367-
defaultValue={name}
368-
autoFocus
369-
onFocus={(e) => e.target.select()}
370-
onKeyDown={(e) =>
371-
handleKeyboard(e, {
372-
stringConfirm: () => handleEditKey((e.target as HTMLInputElement).value),
373-
cancel: handleCancel,
374-
tabForward: () => {
375-
handleEditKey((e.target as HTMLInputElement).value)
376-
const firstChildKey = keyValueArray?.[0][0]
377-
setCurrentlyEditingElement(
378-
firstChildKey
379-
? [...path, firstChildKey]
380-
: getNextOrPrevious(nodeData.fullData, path, 'next', sort)
381-
)
382-
},
383-
tabBack: () => {
384-
handleEditKey((e.target as HTMLInputElement).value)
385-
setCurrentlyEditingElement(getNextOrPrevious(nodeData.fullData, path, 'prev', sort))
386-
},
387-
})
388-
}
389-
style={{ width: `${String(name).length / 1.5 + 0.5}em` }}
390-
/>
391-
) : (
392-
showKey && (
393-
<span
394-
className="jer-key-text"
395-
style={getStyles('property', nodeData)}
396-
onClick={(e) => e.stopPropagation()}
397-
onDoubleClick={() => canEditKey && setCurrentlyEditingElement(path, 'key')}
398-
>
399-
{name === '' ? (
400-
<span className={path.length > 0 ? 'jer-empty-string' : undefined}>
401-
{/* display "<empty string>" using pseudo class CSS */}
402-
</span>
403-
) : (
404-
`${name}:`
405-
)}
406-
</span>
407-
)
408-
)
409-
410363
const EditButtonDisplay = showEditButtons && (
411364
<EditButtons
412365
startEdit={
@@ -429,6 +382,21 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
429382
/>
430383
)
431384

385+
const keyDisplayProps = {
386+
canEditKey,
387+
isEditingKey,
388+
pathString,
389+
path,
390+
name: name as string,
391+
handleKeyboard,
392+
handleEditKey,
393+
handleCancel,
394+
keyValueArray,
395+
styles: getStyles('property', nodeData),
396+
getNextOrPrevious: (type: 'next' | 'prev') =>
397+
getNextOrPrevious(nodeData.fullData, path, type, sort),
398+
}
399+
432400
const CollectionNodeComponent = (
433401
<div
434402
className="jer-component jer-collection-component"
@@ -465,7 +433,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
465433
>
466434
<Icon name="chevron" rotate={collapsed} nodeData={nodeData} />
467435
</div>
468-
{KeyDisplay}
436+
{showKey && <KeyDisplay {...keyDisplayProps} />}
469437
{!isEditing && (
470438
<span
471439
className="jer-brackets jer-bracket-open"
@@ -497,7 +465,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
497465
<></>
498466
) : (
499467
<div className="jer-collection-header-row" style={{ position: 'relative' }}>
500-
{KeyDisplay}
468+
<KeyDisplay {...keyDisplayProps} />
501469
{EditButtonDisplay}
502470
</div>
503471
)}

src/KeyDisplay.tsx

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Component to display the "Property" value for both Collection and Value nodes
3+
*/
4+
5+
import React from 'react'
6+
import { useTreeState } from './contexts'
7+
import { type KeyboardControlsFull, type CollectionKey, type ValueData } from './types'
8+
9+
interface KeyDisplayProps {
10+
canEditKey: boolean
11+
isEditingKey: boolean
12+
pathString: string
13+
path: CollectionKey[]
14+
name: string
15+
handleKeyboard: (
16+
e: React.KeyboardEvent,
17+
eventMap: Partial<Record<keyof KeyboardControlsFull, () => void>>
18+
) => void
19+
handleEditKey: (newKey: string) => void
20+
handleCancel: () => void
21+
keyValueArray?: Array<[string | number, ValueData]>
22+
styles: React.CSSProperties
23+
getNextOrPrevious: (type: 'next' | 'prev') => CollectionKey[] | null
24+
}
25+
26+
export const KeyDisplay: React.FC<KeyDisplayProps> = ({
27+
isEditingKey,
28+
canEditKey,
29+
pathString,
30+
path,
31+
name,
32+
handleKeyboard,
33+
handleEditKey,
34+
handleCancel,
35+
keyValueArray,
36+
styles,
37+
getNextOrPrevious,
38+
}) => {
39+
const { setCurrentlyEditingElement } = useTreeState()
40+
41+
if (!isEditingKey)
42+
return (
43+
<span
44+
className="jer-key-text"
45+
style={{
46+
...styles,
47+
minWidth: `${Math.min(String(name).length + 1, 5)}ch`,
48+
flexShrink: name.length > 10 ? 1 : 0,
49+
}}
50+
onDoubleClick={() => canEditKey && setCurrentlyEditingElement(path, 'key')}
51+
>
52+
{name === '' ? (
53+
<span className={path.length > 0 ? 'jer-empty-string' : undefined}>
54+
{/* display "<empty string>" using pseudo class CSS */}
55+
</span>
56+
) : (
57+
`${name}:`
58+
)}
59+
</span>
60+
)
61+
62+
return (
63+
<input
64+
className="jer-input-text jer-key-edit"
65+
type="text"
66+
name={pathString}
67+
defaultValue={name}
68+
autoFocus
69+
onFocus={(e) => e.target.select()}
70+
onKeyDown={(e: React.KeyboardEvent) =>
71+
handleKeyboard(e, {
72+
stringConfirm: () => handleEditKey((e.target as HTMLInputElement).value),
73+
cancel: handleCancel,
74+
tabForward: () => {
75+
handleEditKey((e.target as HTMLInputElement).value)
76+
if (keyValueArray) {
77+
const firstChildKey = keyValueArray?.[0][0]
78+
setCurrentlyEditingElement(
79+
firstChildKey ? [...path, firstChildKey] : getNextOrPrevious('next')
80+
)
81+
} else setCurrentlyEditingElement(path)
82+
},
83+
tabBack: () => {
84+
handleEditKey((e.target as HTMLInputElement).value)
85+
setCurrentlyEditingElement(getNextOrPrevious('prev'))
86+
},
87+
})
88+
}
89+
style={{ width: `${String(name).length / 1.5 + 0.5}em` }}
90+
/>
91+
)
92+
}

src/ValueNodeWrapper.tsx

+17-48
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useTheme, useTreeState } from './contexts'
2121
import { getCustomNode, type CustomNodeData } from './CustomNode'
2222
import { filterNode, getNextOrPrevious } from './helpers'
2323
import { useCommon, useDragNDrop } from './hooks'
24+
import { KeyDisplay } from './KeyDisplay'
2425

2526
export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
2627
const {
@@ -214,8 +215,7 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
214215
const showErrorString = !isEditing && error
215216
const showTypeSelector = isEditing && allowedDataTypes.length > 0
216217
const showEditButtons = dataType !== 'invalid' && !error && showEditTools
217-
const showKeyEdit = showLabel && isEditingKey
218-
const showKey = showLabel && !isEditingKey && !hideKey
218+
const showKey = showLabel && !hideKey
219219
const showCustomNode = CustomNode && ((isEditing && showOnEdit) || (!isEditing && showOnView))
220220

221221
const inputProps = {
@@ -277,6 +277,20 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
277277
getInputComponent(data, inputProps)
278278
)
279279

280+
const keyDisplayProps = {
281+
canEditKey,
282+
isEditingKey,
283+
pathString,
284+
path,
285+
name: name as string,
286+
handleKeyboard,
287+
handleEditKey,
288+
handleCancel,
289+
styles: getStyles('property', nodeData),
290+
getNextOrPrevious: (type: 'next' | 'prev') =>
291+
getNextOrPrevious(nodeData.fullData, path, type, sort),
292+
}
293+
280294
return (
281295
<div
282296
className="jer-component jer-value-component"
@@ -296,52 +310,7 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
296310
flexWrap: (name as string).length > 10 ? 'wrap' : 'nowrap',
297311
}}
298312
>
299-
{showKey && (
300-
<span
301-
className="jer-key-text"
302-
style={{
303-
...getStyles('property', nodeData),
304-
minWidth: `${Math.min(String(name).length + 1, 5)}ch`,
305-
flexShrink: (name as string).length > 10 ? 1 : 0,
306-
}}
307-
onDoubleClick={() => canEditKey && setCurrentlyEditingElement(path, 'key')}
308-
>
309-
{name === '' ? (
310-
<span className={path.length > 0 ? 'jer-empty-string' : undefined}>
311-
{/* display "<empty string>" using pseudo class CSS */}
312-
</span>
313-
) : (
314-
`${name}:`
315-
)}
316-
</span>
317-
)}
318-
{showKeyEdit && (
319-
<input
320-
className="jer-input-text jer-key-edit"
321-
type="text"
322-
name={pathString}
323-
defaultValue={name}
324-
autoFocus
325-
onFocus={(e) => e.target.select()}
326-
onKeyDown={(e: React.KeyboardEvent) =>
327-
handleKeyboard(e, {
328-
stringConfirm: () => handleEditKey((e.target as HTMLInputElement).value),
329-
cancel: handleCancel,
330-
tabForward: () => {
331-
handleEditKey((e.target as HTMLInputElement).value)
332-
setCurrentlyEditingElement(path)
333-
},
334-
tabBack: () => {
335-
handleEditKey((e.target as HTMLInputElement).value)
336-
setCurrentlyEditingElement(
337-
getNextOrPrevious(nodeData.fullData, path, 'prev', sort)
338-
)
339-
},
340-
})
341-
}
342-
style={{ width: `${String(name).length / 1.5 + 0.5}em` }}
343-
/>
344-
)}
313+
{showKey && <KeyDisplay {...keyDisplayProps} />}
345314
<div className="jer-value-and-buttons">
346315
<div className="jer-input-component">{ValueComponent}</div>
347316
{isEditing ? (

0 commit comments

Comments
 (0)