Skip to content

Commit 30e0e90

Browse files
committed
Update custom components
1 parent e60eb9a commit 30e0e90

File tree

10 files changed

+94
-31
lines changed

10 files changed

+94
-31
lines changed

custom-component-library/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ These are the ones currently available:
2727
- [x] `NaN`
2828
- [x] `BigInt`
2929
- [x] Markdown
30+
- [ ] Image (to-do)
3031

3132
## Development
3233

@@ -50,6 +51,12 @@ Custom components should consider the following:
5051

5152
- Must respect editing restrictions
5253
- If including CSS classes, please prefix with `jer-`
53-
- Handle keyboard input if possible (and double-click to edit)
54+
- Handle keyboard input as much as possible:
55+
- Double-click to edit (if allowed)
56+
- `Tab`/`Shift-Tab` to navigate
57+
- `Enter` to submit
58+
- `Escape` to cancel
5459
- Provide customisation options, particularly styles
5560

61+
If your custom component is "string-like", there are two helper components exported with the package: `StringDisplay` and `StringEdit` -- these are the same components used for the actual "string" elements in the main package. See the [Hyperlink](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/components/Hyperlink/component.tsx) and [BigInt](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/components/BigInt/component.tsx) components for examples of how to use them.
62+

custom-component-library/components/BigInt/component.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const BigIntComponent: React.FC<CustomNodeProps<BigIntProps>> = (props) =
1010
const {
1111
setValue,
1212
isEditing,
13+
setIsEditing,
1314
getStyles,
1415
nodeData,
1516
customNodeProps = {},
@@ -34,6 +35,8 @@ export const BigIntComponent: React.FC<CustomNodeProps<BigIntProps>> = (props) =
3435
}}
3536
/>
3637
) : (
37-
<span style={style}>{value as bigint}</span>
38+
<span onDoubleClick={() => setIsEditing(true)} style={style}>
39+
{value as bigint}
40+
</span>
3841
)
3942
}

custom-component-library/components/Markdown/component.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ export const MarkdownComponent: React.FC<CustomNodeProps<LinkProps>> = (props) =
2323
onClick={(e) => {
2424
if (e.getModifierState('Control') || e.getModifierState('Meta')) setIsEditing(true)
2525
}}
26+
onDoubleClick={() => setIsEditing(true)}
2627
style={styles}
2728
>
28-
{/* TO-DO: Style over-rides, keyboard and double-click behaviour */}
29+
{/* TO-DO: Style over-rides */}
2930
<Markdown>{nodeData.value as string}</Markdown>
3031
</div>
3132
)
Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
import React from 'react'
2-
import { type CustomNodeProps } from '@json-edit-react'
2+
import { useKeyboardListener, type CustomNodeProps } from '@json-edit-react'
33

44
export interface NaNProps {
55
style?: React.CSSProperties
66
}
77

88
export const NotANumberComponent: React.FC<CustomNodeProps<NaNProps>> = ({
9+
isEditing,
10+
setIsEditing,
11+
handleKeyboard,
12+
handleEdit,
13+
keyboardCommon,
914
customNodeProps = {},
10-
}) => <div style={{ color: 'rgb(220, 50, 47)', ...customNodeProps?.style }}>NaN</div>
15+
}) => {
16+
const listenForSubmit = (e: unknown) =>
17+
handleKeyboard(e as React.KeyboardEvent, {
18+
confirm: handleEdit,
19+
...keyboardCommon,
20+
})
21+
22+
useKeyboardListener(isEditing, listenForSubmit)
23+
24+
return (
25+
<div
26+
style={{ color: 'rgb(220, 50, 47)', ...customNodeProps?.style }}
27+
onDoubleClick={() => setIsEditing(true)}
28+
>
29+
NaN
30+
</div>
31+
)
32+
}

custom-component-library/components/Symbol/component.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const SymbolComponent: React.FC<CustomNodeProps<SymbolProps>> = (props) =
1010
const {
1111
setValue,
1212
isEditing,
13+
setIsEditing,
1314
getStyles,
1415
nodeData,
1516
customNodeProps = {},
@@ -35,7 +36,7 @@ export const SymbolComponent: React.FC<CustomNodeProps<SymbolProps>> = (props) =
3536
}}
3637
/>
3738
) : (
38-
<span style={style}>
39+
<span style={style} onDoubleClick={() => setIsEditing(true)}>
3940
Symbol(<span style={descriptionStyle}>"{(nodeData.value as symbol).description}"</span>)
4041
</span>
4142
)
Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
import React from 'react'
2-
import { type CustomNodeProps } from '@json-edit-react'
2+
import { useKeyboardListener, type CustomNodeProps } from '@json-edit-react'
33

44
export interface UndefinedProps {
55
style?: React.CSSProperties
66
}
77

88
export const UndefinedCustomComponent: React.FC<CustomNodeProps<UndefinedProps>> = ({
9+
isEditing,
10+
setIsEditing,
11+
handleKeyboard,
12+
handleEdit,
13+
keyboardCommon,
914
customNodeProps = {},
10-
}) => (
11-
<div style={{ fontStyle: 'italic', color: '#9b9b9b', ...customNodeProps?.style }}>undefined</div>
12-
)
15+
}) => {
16+
const listenForSubmit = (e: unknown) =>
17+
handleKeyboard(e as React.KeyboardEvent, {
18+
confirm: handleEdit,
19+
...keyboardCommon,
20+
})
21+
22+
useKeyboardListener(isEditing, listenForSubmit)
23+
24+
return (
25+
<div
26+
onDoubleClick={() => setIsEditing(true)}
27+
className="jer-value-undefined"
28+
style={{ fontStyle: 'italic', color: '#9b9b9b', ...customNodeProps?.style }}
29+
>
30+
undefined
31+
</div>
32+
)
33+
}

custom-component-library/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Set to true to store date as Date object, false to store as ISO string
2-
const STORE_DATE_AS_DATE_OBJECT = false
2+
const STORE_DATE_AS_DATE_OBJECT = true
33

44
import { useState } from 'react'
55
import {
@@ -29,6 +29,7 @@ function App() {
2929
return (
3030
<div id="container">
3131
<JsonEditor
32+
// restrictEdit={true}
3233
data={data}
3334
setData={setData}
3435
customNodeDefinitions={[

src/ValueNodes.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -286,41 +286,48 @@ export const BooleanValue: React.FC<InputProps & { value: boolean }> = ({
286286
)
287287
}
288288

289-
export const NullValue: React.FC<InputProps> = ({
290-
value,
291-
isEditing,
292-
setIsEditing,
293-
handleEdit,
294-
nodeData,
295-
handleKeyboard,
296-
keyboardCommon,
297-
}) => {
298-
const { getStyles } = useTheme()
289+
// A custom hook to add a keyboard listener to a component that does't have
290+
// standard DOM keyboard behaviour (like inputs). Only used for the `null`
291+
// component here, but is exported for re-use with Custom Components if required
292+
export const useKeyboardListener = (isEditing: boolean, listener: (e: unknown) => void) => {
299293
const timer = useRef<number | undefined>(undefined)
300294

301295
useEffect(() => {
302296
if (!isEditing) {
303-
// The listener messes with other elements when switching rapidly (e.g. when "getNext" called repeatedly on inaccessible elements), so we cancel the listener load before it even happens if this node gets switched from isEditing to not in less than 100ms
297+
// The listener messes with other elements when switching rapidly (e.g.
298+
// when "getNext" is called repeatedly on inaccessible elements), so we
299+
// cancel the listener load before it even happens if this node gets
300+
// switched from isEditing to not in less than 100ms
304301
window.clearTimeout(timer.current)
305302
return
306303
}
307304
// Small delay to prevent registering keyboard input from previous element
308305
// if switched using "Tab"
309-
timer.current = window.setTimeout(
310-
() => window.addEventListener('keydown', listenForSubmit),
311-
100
312-
)
306+
timer.current = window.setTimeout(() => window.addEventListener('keydown', listener), 100)
307+
308+
return () => window.removeEventListener('keydown', listener)
309+
}, [isEditing, listener])
310+
}
313311

314-
return () => window.removeEventListener('keydown', listenForSubmit)
315-
// eslint-disable-next-line react-hooks/exhaustive-deps
316-
}, [isEditing])
312+
export const NullValue: React.FC<InputProps> = ({
313+
value,
314+
isEditing,
315+
setIsEditing,
316+
handleEdit,
317+
nodeData,
318+
handleKeyboard,
319+
keyboardCommon,
320+
}) => {
321+
const { getStyles } = useTheme()
317322

318323
const listenForSubmit = (e: unknown) =>
319324
handleKeyboard(e as React.KeyboardEvent, {
320325
confirm: handleEdit,
321326
...keyboardCommon,
322327
})
323328

329+
useKeyboardListener(isEditing, listenForSubmit)
330+
324331
return (
325332
<div
326333
onDoubleClick={() => setIsEditing(true)}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export { JsonEditor } from './JsonEditor'
22
export { defaultTheme } from './contexts/ThemeProvider'
33
export { IconAdd, IconEdit, IconDelete, IconCopy, IconOk, IconCancel, IconChevron } from './Icons'
4-
export { StringDisplay, StringEdit } from './ValueNodes'
4+
export { StringDisplay, StringEdit, useKeyboardListener } from './ValueNodes'
55
export { LinkCustomComponent, LinkCustomNodeDefinition } from './customComponents'
66
export { matchNode, matchNodeKey, isCollection, toPathString } from './helpers'
77
export { default as assign } from 'object-property-assigner'

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ interface BaseNodeProps {
287287
}
288288

289289
export interface CollectionNodeProps extends BaseNodeProps {
290-
mainContainerRef: React.MutableRefObject<Element>
290+
mainContainerRef: React.RefObject<Element>
291291
data: CollectionData
292292
collapseFilter: FilterFunction
293293
collapseAnimationTime: number

0 commit comments

Comments
 (0)