Skip to content

Commit 98167a7

Browse files
authored
#176 Additional event functions (#178)
1 parent 194f157 commit 98167a7

File tree

7 files changed

+73
-25
lines changed

7 files changed

+73
-25
lines changed

README.md

Lines changed: 21 additions & 19 deletions
Large diffs are not rendered by default.

demo/src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,9 @@ function App() {
486486
)
487487
: undefined
488488
}
489+
onEditEvent={(path, isKey) => console.log('Editing path', path, isKey)}
490+
onCollapse={(input) => console.log('Collapse', input)}
491+
// collapseClickZones={['property', 'header']}
489492
/>
490493
</Box>
491494
<VStack w="100%" align="flex-end" gap={4}>

src/CollectionNode.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
6161
keyboardControls,
6262
handleKeyboard,
6363
insertAtTop,
64+
onCollapse,
65+
collapseClickZones,
6466
} = props
6567
const [stringifiedValue, setStringifiedValue] = useState(jsonStringify(data))
6668

@@ -174,6 +176,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
174176
}
175177

176178
const handleCollapse = (e: React.MouseEvent) => {
179+
e.stopPropagation()
177180
const modifier = getModifier(e)
178181
if (modifier && keyboardControls.collapseModifier.includes(modifier)) {
179182
hasBeenOpened.current = true
@@ -183,6 +186,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
183186
if (!(currentlyEditingElement && currentlyEditingElement.includes(pathString))) {
184187
hasBeenOpened.current = true
185188
setCollapseState(null)
189+
if (onCollapse) onCollapse({ path, collapsed: !collapsed, includesChildren: false })
186190
animateCollapse(!collapsed)
187191
}
188192
}
@@ -404,6 +408,9 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
404408
styles: getStyles('property', nodeData),
405409
getNextOrPrevious: (type: 'next' | 'prev') =>
406410
getNextOrPrevious(nodeData.fullData, path, type, sort),
411+
handleClick: collapseClickZones.includes('property')
412+
? handleCollapse
413+
: (e: React.MouseEvent) => e.stopPropagation(),
407414
}
408415

409416
const CollectionNodeComponent = (
@@ -424,21 +431,21 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
424431
width: `${indent / 2 + 1}em`,
425432
zIndex: 10 + nodeData.level * 2,
426433
}}
427-
onClick={(e) => handleCollapse(e)}
434+
onClick={collapseClickZones.includes('left') ? handleCollapse : undefined}
428435
/>
429436
{!isEditing && BottomDropTarget}
430437
<DropTargetPadding position="above" nodeData={nodeData} />
431438
{showCollectionWrapper ? (
432439
<div
433440
className="jer-collection-header-row"
434441
style={{ position: 'relative' }}
435-
onClick={(e) => handleCollapse(e)}
442+
onClick={collapseClickZones.includes('header') ? handleCollapse : undefined}
436443
>
437444
<div className="jer-collection-name">
438445
<div
439446
className={`jer-collapse-icon jer-accordion-icon${collapsed ? ' jer-rotate-90' : ''}`}
440447
style={{ zIndex: 11 + nodeData.level * 2, transition: cssTransitionValue }}
441-
onClick={(e) => handleCollapse(e)}
448+
onClick={handleCollapse}
442449
>
443450
<Icon name="chevron" rotate={collapsed} nodeData={nodeData} />
444451
</div>

src/JsonEditor.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ const Editor: React.FC<JsonEditorProps> = ({
7575
errorMessageTimeout = 2500,
7676
keyboardControls = {},
7777
insertAtTop = false,
78+
onCollapse,
79+
collapseClickZones = ['header', 'left'],
7880
}) => {
7981
const { getStyles } = useTheme()
8082
const { setCurrentlyEditingElement } = useTreeState()
@@ -351,6 +353,8 @@ const Editor: React.FC<JsonEditorProps> = ({
351353
object: insertAtTop === true || insertAtTop === 'object',
352354
array: insertAtTop === true || insertAtTop === 'array',
353355
},
356+
onCollapse,
357+
collapseClickZones,
354358
}
355359

356360
const mainContainerStyles = { ...getStyles('container', nodeData), minWidth, maxWidth }
@@ -393,7 +397,7 @@ export const JsonEditor: React.FC<JsonEditorProps> = (props) => {
393397

394398
return (
395399
<ThemeProvider theme={props.theme ?? defaultTheme} icons={props.icons} docRoot={docRoot}>
396-
<TreeStateProvider>
400+
<TreeStateProvider onEditEvent={props.onEditEvent} onCollapse={props.onCollapse}>
397401
<Editor {...props} />
398402
</TreeStateProvider>
399403
</ThemeProvider>

src/KeyDisplay.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface KeyDisplayProps {
1818
) => void
1919
handleEditKey: (newKey: string) => void
2020
handleCancel: () => void
21+
handleClick?: (e: React.MouseEvent) => void
2122
keyValueArray?: Array<[string | number, ValueData]>
2223
styles: React.CSSProperties
2324
getNextOrPrevious: (type: 'next' | 'prev') => CollectionKey[] | null
@@ -32,6 +33,7 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
3233
handleKeyboard,
3334
handleEditKey,
3435
handleCancel,
36+
handleClick,
3537
keyValueArray,
3638
styles,
3739
getNextOrPrevious,
@@ -48,6 +50,7 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
4850
flexShrink: name.length > 10 ? 1 : 0,
4951
}}
5052
onDoubleClick={() => canEditKey && setCurrentlyEditingElement(path, 'key')}
53+
onClick={handleClick}
5154
>
5255
{name === '' ? (
5356
<span className={path.length > 0 ? 'jer-empty-string' : undefined}>

src/contexts/TreeStateProvider.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
*/
99

1010
import React, { createContext, useContext, useRef, useState } from 'react'
11-
import { type TabDirection, type CollectionKey, type JsonData } from '../types'
11+
import {
12+
type TabDirection,
13+
type CollectionKey,
14+
type JsonData,
15+
type OnEditEventFunction,
16+
type OnCollapseFunction,
17+
} from '../types'
1218
import { toPathString } from '../helpers'
1319

1420
interface CollapseAllState {
@@ -59,7 +65,13 @@ const initialContext: TreeStateContext = {
5965

6066
const TreeStateProviderContext = createContext(initialContext)
6167

62-
export const TreeStateProvider = ({ children }: { children: React.ReactNode }) => {
68+
interface TreeStateProps {
69+
children: React.ReactNode
70+
onEditEvent?: OnEditEventFunction
71+
onCollapse?: OnCollapseFunction
72+
}
73+
74+
export const TreeStateProvider = ({ children, onEditEvent, onCollapse }: TreeStateProps) => {
6375
const [collapseState, setCollapseState] = useState<CollapseAllState | null>(null)
6476
const [currentlyEditingElement, setCurrentlyEditingElement] = useState<string | null>(null)
6577

@@ -98,6 +110,7 @@ export const TreeStateProvider = ({ children }: { children: React.ReactNode }) =
98110
cancelOp.current()
99111
}
100112
setCurrentlyEditingElement(pathString)
113+
if (onEditEvent) onEditEvent(path, newCancelOrKey === 'key')
101114
cancelOp.current = typeof newCancelOrKey === 'function' ? newCancelOrKey : null
102115
}
103116

@@ -121,6 +134,8 @@ export const TreeStateProvider = ({ children }: { children: React.ReactNode }) =
121134
collapseState,
122135
setCollapseState: (state) => {
123136
setCollapseState(state)
137+
if (onCollapse && state !== null)
138+
onCollapse({ path: state.path, collapsed: state.collapsed, includesChildren: true })
124139
// Reset after 2 seconds, which is enough time for all child nodes to
125140
// have opened/closed, but still allows collapse reset if data changes
126141
// externally

src/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ export interface JsonEditorProps {
5050
errorMessageTimeout?: number // ms
5151
keyboardControls?: KeyboardControls
5252
insertAtTop?: boolean | 'array' | 'object'
53+
collapseClickZones?: Array<'left' | 'header' | 'property'>
54+
// Additional events
55+
onEditEvent?: OnEditEventFunction
56+
onCollapse?: OnCollapseFunction
5357
}
5458

5559
const ValueDataTypes = ['string', 'number', 'boolean', 'null'] as const
@@ -157,6 +161,14 @@ export type CompareFunction = (
157161

158162
export type SortFunction = <T>(arr: T[], nodeMap: (input: T) => [string | number, unknown]) => void
159163

164+
export type OnEditEventFunction = (path: CollectionKey[] | string | null, isKey: boolean) => void
165+
166+
export type OnCollapseFunction = (input: {
167+
path: CollectionKey[]
168+
collapsed: boolean
169+
includesChildren: boolean
170+
}) => void
171+
160172
// Internal update
161173
export type InternalUpdateFunction = (
162174
value: unknown,
@@ -263,6 +275,8 @@ export interface CollectionNodeProps extends BaseNodeProps {
263275
jsonStringify: (data: JsonData) => string
264276
insertAtTop: { object: boolean; array: boolean }
265277
TextEditor?: React.FC<TextEditorProps>
278+
onCollapse?: OnCollapseFunction
279+
collapseClickZones: Array<'left' | 'header' | 'property'>
266280
}
267281

268282
export type ValueData = string | number | boolean

0 commit comments

Comments
 (0)