Skip to content

Commit 1a757e8

Browse files
committed
feat: improve built-in editor with autoFocus and better keyboard control
1 parent c2282dd commit 1a757e8

File tree

4 files changed

+69
-34
lines changed

4 files changed

+69
-34
lines changed

docs/pages/how-to/data-types.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ For advanced use cases, you can provide `PreComponent` and `PostComponent` to re
3737

3838
To enable editing for a data type, you need to provide `serialize` and `deserialize` functions to convert the value to and from a string representation. You can then use the `Editor` component to provide a custom editor for the stringified value. When the user edits the value, it will be parsed using `deserialize`, and the result will be passed to the `onChange` callback.
3939

40+
- `props.value` - The value to edit.
41+
- `props.setValue` - A function that can be used to update the value.
42+
- `props.abortEditing` - A function that can be used to abort editing.
43+
- `props.commitEditing` - A function that can be used to commit the value and finish editing.
44+
4045
## Examples
4146

4247
### Adding support for image

src/components/DataKeyPair.tsx

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -134,33 +134,42 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
134134
}
135135
}, [highlightColor, isHighlight, prevValue, value])
136136

137+
const startEditing = useCallback((event: MouseEvent) => {
138+
event.preventDefault()
139+
if (serialize) setTempValue(serialize(value))
140+
setEditing(true)
141+
}, [serialize, value])
142+
143+
const abortEditing = useCallback(() => {
144+
setEditing(false)
145+
setTempValue('')
146+
}, [setEditing, setTempValue])
147+
148+
const commitEditing = useCallback((newValue: string) => {
149+
setEditing(false)
150+
if (!deserialize) return
151+
152+
try {
153+
onChange(path, value, deserialize(newValue))
154+
} catch (e) {
155+
// do nothing when deserialize failed
156+
}
157+
}, [setEditing, deserialize, onChange, path, value])
158+
137159
const actionIcons = useMemo(() => {
138-
if (editing && deserialize) {
160+
if (editing) {
139161
return (
140162
<>
141163
<IconBox>
142164
<CloseIcon
143165
sx={{ fontSize: '.8rem' }}
144-
onClick={() => {
145-
// abort editing
146-
setEditing(false)
147-
setTempValue('')
148-
}}
166+
onClick={abortEditing}
149167
/>
150168
</IconBox>
151169
<IconBox>
152170
<CheckIcon
153171
sx={{ fontSize: '.8rem' }}
154-
onClick={() => {
155-
// finish editing, save data
156-
setEditing(false)
157-
try {
158-
const newValue = deserialize(tempValue)
159-
onChange(path, value, newValue)
160-
} catch (e) {
161-
// do nothing when deserialize failed
162-
}
163-
}}
172+
onClick={() => commitEditing(tempValue)}
164173
/>
165174
</IconBox>
166175
</>
@@ -191,11 +200,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
191200
{(Editor && editable && serialize && deserialize) &&
192201
(
193202
<IconBox
194-
onClick={event => {
195-
event.preventDefault()
196-
setTempValue(serialize(value))
197-
setEditing(true)
198-
}}
203+
onClick={startEditing}
199204
>
200205
<EditIcon sx={{ fontSize: '.8rem' }} />
201206
</IconBox>
@@ -212,10 +217,12 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
212217
editable,
213218
editing,
214219
enableClipboard,
215-
onChange,
216-
path,
217220
tempValue,
218-
value
221+
path,
222+
value,
223+
startEditing,
224+
abortEditing,
225+
commitEditing
219226
])
220227

221228
const isEmptyValue = useMemo(() => getValueSize(value) === 0, [value])
@@ -310,7 +317,14 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
310317
</DataBox>
311318
{
312319
(editing && editable)
313-
? (Editor && <Editor value={tempValue} setValue={setTempValue} />)
320+
? (Editor && (
321+
<Editor
322+
value={tempValue}
323+
setValue={setTempValue}
324+
abortEditing={abortEditing}
325+
commitEditing={commitEditing}
326+
/>
327+
))
314328
: (Component)
315329
? <Component {...downstreamProps} />
316330
: (
@@ -322,6 +336,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
322336
{PostComponent && <PostComponent {...downstreamProps} />}
323337
{(isHover && expandable && !inspect) && actionIcons}
324338
{(isHover && !expandable) && actionIcons}
339+
{(!isHover && editing) && actionIcons}
325340
</Box>
326341
)
327342
}

src/components/DataTypes/defineEasyType.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InputBase } from '@mui/material'
2-
import type { ChangeEventHandler, ComponentType, FC } from 'react'
2+
import type { ChangeEventHandler, ComponentType, FC, KeyboardEvent } from 'react'
33
import { memo, useCallback } from 'react'
44

55
import { useJsonViewerStore } from '../../stores/JsonViewerStore'
@@ -64,18 +64,31 @@ export function defineEasyType<Value> ({
6464
}
6565
}
6666

67-
const EasyTypeEditor: FC<EditorProps<string>> = ({ value, setValue }) => {
67+
const EasyTypeEditor: FC<EditorProps<string>> = ({ value, setValue, abortEditing, commitEditing }) => {
6868
const color = useJsonViewerStore(store => store.colorspace[colorKey])
69+
70+
const handleKeyDown = useCallback((event: KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) => {
71+
if (event.key === 'Enter') {
72+
event.preventDefault()
73+
commitEditing(value)
74+
}
75+
76+
if (event.key === 'Escape') {
77+
event.preventDefault()
78+
abortEditing()
79+
}
80+
}, [abortEditing, commitEditing, value])
81+
82+
const handleChange = useCallback<ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>>((event) => {
83+
setValue(event.target.value)
84+
}, [setValue])
85+
6986
return (
7087
<InputBase
88+
autoFocus
7189
value={value}
72-
onChange={
73-
useCallback<ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>>(
74-
(event) => {
75-
setValue(event.target.value)
76-
}, [setValue]
77-
)
78-
}
90+
onChange={handleChange}
91+
onKeyDown={handleKeyDown}
7992
size='small'
8093
multiline
8194
sx={{

src/type.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export interface DataItemProps<ValueType = unknown> {
4848
export type EditorProps<ValueType = unknown> = {
4949
value: ValueType
5050
setValue: Dispatch<ValueType>
51+
abortEditing: () => void
52+
commitEditing: (newValue: string) => void
5153
}
5254

5355
/**

0 commit comments

Comments
 (0)