Skip to content

#176 Additional event functions #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions README.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ function App() {
)
: undefined
}
onEditEvent={(path, isKey) => console.log('Editing path', path, isKey)}
onCollapse={(input) => console.log('Collapse', input)}
// collapseClickZones={['property', 'header']}
/>
</Box>
<VStack w="100%" align="flex-end" gap={4}>
Expand Down
13 changes: 10 additions & 3 deletions src/CollectionNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
keyboardControls,
handleKeyboard,
insertAtTop,
onCollapse,
collapseClickZones,
} = props
const [stringifiedValue, setStringifiedValue] = useState(jsonStringify(data))

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

const handleCollapse = (e: React.MouseEvent) => {
e.stopPropagation()
const modifier = getModifier(e)
if (modifier && keyboardControls.collapseModifier.includes(modifier)) {
hasBeenOpened.current = true
Expand All @@ -183,6 +186,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
if (!(currentlyEditingElement && currentlyEditingElement.includes(pathString))) {
hasBeenOpened.current = true
setCollapseState(null)
if (onCollapse) onCollapse({ path, collapsed: !collapsed, includesChildren: false })
animateCollapse(!collapsed)
}
}
Expand Down Expand Up @@ -404,6 +408,9 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
styles: getStyles('property', nodeData),
getNextOrPrevious: (type: 'next' | 'prev') =>
getNextOrPrevious(nodeData.fullData, path, type, sort),
handleClick: collapseClickZones.includes('property')
? handleCollapse
: (e: React.MouseEvent) => e.stopPropagation(),
}

const CollectionNodeComponent = (
Expand All @@ -424,21 +431,21 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
width: `${indent / 2 + 1}em`,
zIndex: 10 + nodeData.level * 2,
}}
onClick={(e) => handleCollapse(e)}
onClick={collapseClickZones.includes('left') ? handleCollapse : undefined}
/>
{!isEditing && BottomDropTarget}
<DropTargetPadding position="above" nodeData={nodeData} />
{showCollectionWrapper ? (
<div
className="jer-collection-header-row"
style={{ position: 'relative' }}
onClick={(e) => handleCollapse(e)}
onClick={collapseClickZones.includes('header') ? handleCollapse : undefined}
>
<div className="jer-collection-name">
<div
className={`jer-collapse-icon jer-accordion-icon${collapsed ? ' jer-rotate-90' : ''}`}
style={{ zIndex: 11 + nodeData.level * 2, transition: cssTransitionValue }}
onClick={(e) => handleCollapse(e)}
onClick={handleCollapse}
>
<Icon name="chevron" rotate={collapsed} nodeData={nodeData} />
</div>
Expand Down
6 changes: 5 additions & 1 deletion src/JsonEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ const Editor: React.FC<JsonEditorProps> = ({
errorMessageTimeout = 2500,
keyboardControls = {},
insertAtTop = false,
onCollapse,
collapseClickZones = ['header', 'left'],
}) => {
const { getStyles } = useTheme()
const { setCurrentlyEditingElement } = useTreeState()
Expand Down Expand Up @@ -351,6 +353,8 @@ const Editor: React.FC<JsonEditorProps> = ({
object: insertAtTop === true || insertAtTop === 'object',
array: insertAtTop === true || insertAtTop === 'array',
},
onCollapse,
collapseClickZones,
}

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

return (
<ThemeProvider theme={props.theme ?? defaultTheme} icons={props.icons} docRoot={docRoot}>
<TreeStateProvider>
<TreeStateProvider onEditEvent={props.onEditEvent} onCollapse={props.onCollapse}>
<Editor {...props} />
</TreeStateProvider>
</ThemeProvider>
Expand Down
3 changes: 3 additions & 0 deletions src/KeyDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface KeyDisplayProps {
) => void
handleEditKey: (newKey: string) => void
handleCancel: () => void
handleClick?: (e: React.MouseEvent) => void
keyValueArray?: Array<[string | number, ValueData]>
styles: React.CSSProperties
getNextOrPrevious: (type: 'next' | 'prev') => CollectionKey[] | null
Expand All @@ -32,6 +33,7 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
handleKeyboard,
handleEditKey,
handleCancel,
handleClick,
keyValueArray,
styles,
getNextOrPrevious,
Expand All @@ -48,6 +50,7 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
flexShrink: name.length > 10 ? 1 : 0,
}}
onDoubleClick={() => canEditKey && setCurrentlyEditingElement(path, 'key')}
onClick={handleClick}
>
{name === '' ? (
<span className={path.length > 0 ? 'jer-empty-string' : undefined}>
Expand Down
19 changes: 17 additions & 2 deletions src/contexts/TreeStateProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
*/

import React, { createContext, useContext, useRef, useState } from 'react'
import { type TabDirection, type CollectionKey, type JsonData } from '../types'
import {
type TabDirection,
type CollectionKey,
type JsonData,
type OnEditEventFunction,
type OnCollapseFunction,
} from '../types'
import { toPathString } from '../helpers'

interface CollapseAllState {
Expand Down Expand Up @@ -59,7 +65,13 @@ const initialContext: TreeStateContext = {

const TreeStateProviderContext = createContext(initialContext)

export const TreeStateProvider = ({ children }: { children: React.ReactNode }) => {
interface TreeStateProps {
children: React.ReactNode
onEditEvent?: OnEditEventFunction
onCollapse?: OnCollapseFunction
}

export const TreeStateProvider = ({ children, onEditEvent, onCollapse }: TreeStateProps) => {
const [collapseState, setCollapseState] = useState<CollapseAllState | null>(null)
const [currentlyEditingElement, setCurrentlyEditingElement] = useState<string | null>(null)

Expand Down Expand Up @@ -98,6 +110,7 @@ export const TreeStateProvider = ({ children }: { children: React.ReactNode }) =
cancelOp.current()
}
setCurrentlyEditingElement(pathString)
if (onEditEvent) onEditEvent(path, newCancelOrKey === 'key')
cancelOp.current = typeof newCancelOrKey === 'function' ? newCancelOrKey : null
}

Expand All @@ -121,6 +134,8 @@ export const TreeStateProvider = ({ children }: { children: React.ReactNode }) =
collapseState,
setCollapseState: (state) => {
setCollapseState(state)
if (onCollapse && state !== null)
onCollapse({ path: state.path, collapsed: state.collapsed, includesChildren: true })
// Reset after 2 seconds, which is enough time for all child nodes to
// have opened/closed, but still allows collapse reset if data changes
// externally
Expand Down
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export interface JsonEditorProps {
errorMessageTimeout?: number // ms
keyboardControls?: KeyboardControls
insertAtTop?: boolean | 'array' | 'object'
collapseClickZones?: Array<'left' | 'header' | 'property'>
// Additional events
onEditEvent?: OnEditEventFunction
onCollapse?: OnCollapseFunction
}

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

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

export type OnEditEventFunction = (path: CollectionKey[] | string | null, isKey: boolean) => void

export type OnCollapseFunction = (input: {
path: CollectionKey[]
collapsed: boolean
includesChildren: boolean
}) => void

// Internal update
export type InternalUpdateFunction = (
value: unknown,
Expand Down Expand Up @@ -263,6 +275,8 @@ export interface CollectionNodeProps extends BaseNodeProps {
jsonStringify: (data: JsonData) => string
insertAtTop: { object: boolean; array: boolean }
TextEditor?: React.FC<TextEditorProps>
onCollapse?: OnCollapseFunction
collapseClickZones: Array<'left' | 'header' | 'property'>
}

export type ValueData = string | number | boolean
Expand Down