Skip to content
Open
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
8 changes: 8 additions & 0 deletions src/CollectionNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState, useRef, useCallback } from 'react'
import { ValueNodeWrapper } from './ValueNodeWrapper'
import { EditButtons, InputButtons } from './ButtonPanels'
import { getCustomNode } from './CustomNode'
import { getCustomKey } from './CustomKey'
import {
type CollectionNodeProps,
type NodeData,
Expand Down Expand Up @@ -60,6 +61,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
translate,
customNodeDefinitions,
customNodeData,
customKeyDefinitions,
customKeyData,
jsonParse,
jsonStringify,
TextEditor,
Expand Down Expand Up @@ -308,6 +311,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
}

const childCustomNodeData = getCustomNode(customNodeDefinitions, childNodeData)
const childCustomKeyData = getCustomKey(customKeyDefinitions, childNodeData)

return (
<div
Expand All @@ -325,6 +329,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
showCollectionCount={showCollectionCount}
canDragOnto={canEdit}
customNodeData={childCustomNodeData}
customKeyData={childCustomKeyData}
/>
) : (
<ValueNodeWrapper
Expand All @@ -336,6 +341,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
canDragOnto={canEdit}
showLabel={collectionType === 'object' ? true : showArrayIndices}
customNodeData={childCustomNodeData}
customKeyData={childCustomKeyData}
/>
)}
</div>
Expand Down Expand Up @@ -461,6 +467,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
// but "header" is
(e: React.MouseEvent) => e.stopPropagation(),
emptyStringKey,
nodeData,
customKeyData,
}

const CollectionNodeComponent = (
Expand Down
23 changes: 23 additions & 0 deletions src/CustomKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { type CustomKeyDefinition, type CustomKeyProps, type NodeData } from './types'

export interface CustomKeyData {
CustomKey?: React.FC<CustomKeyProps>
customKeyProps?: Record<string, unknown>
}

// Fetches matching custom key definition (based on condition filter) and
// returns the component and its props
export const getCustomKey = (
customKeyDefinitions: CustomKeyDefinition[] = [],
nodeData: NodeData
): CustomKeyData => {
const matchingDefinitions = customKeyDefinitions.filter(({ condition }) => condition(nodeData))
if (matchingDefinitions.length === 0) return {}

const { element, customKeyProps } = matchingDefinitions[0]

return {
CustomKey: element,
customKeyProps,
}
}
5 changes: 5 additions & 0 deletions src/JsonEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { ValueNodeWrapper } from './ValueNodeWrapper'

import './style.css'
import { getCustomNode } from './CustomNode'
import { getCustomKey } from './CustomKey'

const Editor: React.FC<JsonEditorProps> = ({
data: srcData,
Expand Down Expand Up @@ -77,6 +78,7 @@ const Editor: React.FC<JsonEditorProps> = ({
id,
customText = {},
customNodeDefinitions = [],
customKeyDefinitions = [],
customButtons = [],
jsonParse = JSON.parse,
jsonStringify = (data, replacer) => JSON.stringify(data, replacer, 2),
Expand Down Expand Up @@ -341,6 +343,7 @@ const Editor: React.FC<JsonEditorProps> = ({
)

const customNodeData = getCustomNode(customNodeDefinitions, nodeData)
const customKeyData = getCustomKey(customKeyDefinitions, nodeData)

const otherProps = {
mainContainerRef: mainContainerRef as React.MutableRefObject<Element>,
Expand Down Expand Up @@ -379,6 +382,8 @@ const Editor: React.FC<JsonEditorProps> = ({
translate,
customNodeDefinitions,
customNodeData,
customKeyDefinitions,
customKeyData,
customButtons,
parentData: null,
jsonParse: jsonParseReplacement,
Expand Down
33 changes: 31 additions & 2 deletions src/KeyDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

import React from 'react'
import { useTreeState } from './contexts'
import { type KeyboardControlsFull, type CollectionKey, type ValueData } from './types'
import {
type KeyboardControlsFull,
type CollectionKey,
type ValueData,
type NodeData,
} from './types'
import { type CustomKeyData } from './CustomKey'

interface KeyDisplayProps {
canEditKey: boolean
Expand All @@ -24,6 +30,8 @@ interface KeyDisplayProps {
styles: React.CSSProperties
getNextOrPrevious: (type: 'next' | 'prev') => CollectionKey[] | null
emptyStringKey: string | null
nodeData?: NodeData
customKeyData?: CustomKeyData
}

export const KeyDisplay: React.FC<KeyDisplayProps> = ({
Expand All @@ -41,12 +49,32 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
styles,
getNextOrPrevious,
emptyStringKey,
nodeData,
customKeyData,
}) => {
const { setCurrentlyEditingElement } = useTreeState()

const displayKey = typeof name === 'number' ? String(name + (arrayIndexFromOne ? 1 : 0)) : name

if (!isEditingKey)
if (!isEditingKey) {
if (customKeyData?.CustomKey && nodeData) {
const { CustomKey, customKeyProps } = customKeyData
return (
<CustomKey
nodeData={nodeData}
name={name}
path={path}
value={nodeData.value}
styles={styles}
isEditingKey={false}
canEditKey={canEditKey}
handleEditKey={handleEditKey}
handleClick={handleClick}
customKeyProps={customKeyProps}
/>
)
}

return (
<span
className="jer-key-text"
Expand All @@ -62,6 +90,7 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
{displayKey !== '' || emptyStringKey ? <span className="jer-key-colon">:</span> : null}
</span>
)
}

return (
<input
Expand Down
2 changes: 2 additions & 0 deletions src/ValueNodeWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
getNextOrPrevious: (type: 'next' | 'prev') =>
getNextOrPrevious(nodeData.fullData, path, type, sort),
emptyStringKey,
nodeData,
customKeyData: props.customKeyData,
}

const ValueComponent = showCustomNode ? (
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export {
type ValueNodeProps,
type CustomNodeProps,
type CustomNodeDefinition,
type CustomKeyProps,
type CustomKeyDefinition,
type CustomTextDefinitions,
type CustomTextFunction,
type Theme,
Expand Down
25 changes: 25 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type Options as AssignOptions } from 'object-property-assigner'
import { type LocalisedStrings, type TranslateFunction } from './localisation'
import { type ExternalTriggers } from './hooks'
import { CustomNodeData } from './CustomNode'
import { CustomKeyData } from './CustomKey'

export type JsonData = Record<string, unknown> | Array<unknown> | unknown

Expand Down Expand Up @@ -51,6 +52,8 @@ export interface JsonEditorProps {
// enforcing consistency between the component and the definition that uses it
// eslint-disable-next-line @typescript-eslint/no-explicit-any
customNodeDefinitions?: CustomNodeDefinition<Record<string, any>, Record<string, any>>[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
customKeyDefinitions?: CustomKeyDefinition<Record<string, any>>[]
customText?: CustomTextDefinitions
customButtons?: CustomButtonDefinition[]
jsonParse?: (input: string, reviver?: (key: string, value: string) => unknown) => JsonData
Expand Down Expand Up @@ -278,6 +281,8 @@ interface BaseNodeProps {
translate: TranslateFunction
customNodeDefinitions: CustomNodeDefinition[]
customNodeData: CustomNodeData
customKeyDefinitions: CustomKeyDefinition[]
customKeyData: CustomKeyData
customButtons: CustomButtonDefinition[]
errorMessageTimeout: number
keyboardControls: KeyboardControlsFull
Expand Down Expand Up @@ -367,6 +372,26 @@ export interface CustomNodeDefinition<T = Record<string, unknown>, U = Record<st
parseReviver?: (stringified: string) => unknown
}

export interface CustomKeyProps<T = Record<string, unknown>> {
nodeData: NodeData
name: string | number
path: CollectionKey[]
value: JsonData
styles: React.CSSProperties
isEditingKey: boolean
canEditKey: boolean
handleEditKey: (newKey: string) => void
handleClick?: (e: React.MouseEvent) => void
customKeyProps?: T
}

export interface CustomKeyDefinition<T = Record<string, unknown>> {
condition: FilterFunction
element: React.FC<CustomKeyProps<T>>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
customKeyProps?: T
}

export type CustomTextDefinitions = Partial<{ [key in keyof LocalisedStrings]: CustomTextFunction }>

export interface CustomButtonDefinition {
Expand Down