Skip to content

Commit

Permalink
feat: improve built-in editor with autoFocus and better keyboard control
Browse files Browse the repository at this point in the history
  • Loading branch information
pionxzh committed Oct 9, 2023
1 parent c2282dd commit 1a757e8
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 34 deletions.
5 changes: 5 additions & 0 deletions docs/pages/how-to/data-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ For advanced use cases, you can provide `PreComponent` and `PostComponent` to re

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.

- `props.value` - The value to edit.
- `props.setValue` - A function that can be used to update the value.
- `props.abortEditing` - A function that can be used to abort editing.
- `props.commitEditing` - A function that can be used to commit the value and finish editing.

## Examples

### Adding support for image
Expand Down
65 changes: 40 additions & 25 deletions src/components/DataKeyPair.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,33 +134,42 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
}
}, [highlightColor, isHighlight, prevValue, value])

const startEditing = useCallback((event: MouseEvent) => {
event.preventDefault()
if (serialize) setTempValue(serialize(value))
setEditing(true)
}, [serialize, value])

const abortEditing = useCallback(() => {
setEditing(false)
setTempValue('')
}, [setEditing, setTempValue])

const commitEditing = useCallback((newValue: string) => {
setEditing(false)
if (!deserialize) return

try {
onChange(path, value, deserialize(newValue))
} catch (e) {
// do nothing when deserialize failed
}
}, [setEditing, deserialize, onChange, path, value])

const actionIcons = useMemo(() => {
if (editing && deserialize) {
if (editing) {
return (
<>
<IconBox>
<CloseIcon
sx={{ fontSize: '.8rem' }}
onClick={() => {
// abort editing
setEditing(false)
setTempValue('')
}}
onClick={abortEditing}
/>
</IconBox>
<IconBox>
<CheckIcon
sx={{ fontSize: '.8rem' }}
onClick={() => {
// finish editing, save data
setEditing(false)
try {
const newValue = deserialize(tempValue)
onChange(path, value, newValue)
} catch (e) {
// do nothing when deserialize failed
}
}}
onClick={() => commitEditing(tempValue)}
/>
</IconBox>
</>
Expand Down Expand Up @@ -191,11 +200,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
{(Editor && editable && serialize && deserialize) &&
(
<IconBox
onClick={event => {
event.preventDefault()
setTempValue(serialize(value))
setEditing(true)
}}
onClick={startEditing}
>
<EditIcon sx={{ fontSize: '.8rem' }} />
</IconBox>
Expand All @@ -212,10 +217,12 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
editable,
editing,
enableClipboard,
onChange,
path,
tempValue,
value
path,
value,
startEditing,
abortEditing,
commitEditing
])

const isEmptyValue = useMemo(() => getValueSize(value) === 0, [value])
Expand Down Expand Up @@ -310,7 +317,14 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
</DataBox>
{
(editing && editable)
? (Editor && <Editor value={tempValue} setValue={setTempValue} />)
? (Editor && (
<Editor
value={tempValue}
setValue={setTempValue}
abortEditing={abortEditing}
commitEditing={commitEditing}
/>
))
: (Component)
? <Component {...downstreamProps} />
: (
Expand All @@ -322,6 +336,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
{PostComponent && <PostComponent {...downstreamProps} />}
{(isHover && expandable && !inspect) && actionIcons}
{(isHover && !expandable) && actionIcons}
{(!isHover && editing) && actionIcons}
</Box>
)
}
31 changes: 22 additions & 9 deletions src/components/DataTypes/defineEasyType.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InputBase } from '@mui/material'
import type { ChangeEventHandler, ComponentType, FC } from 'react'
import type { ChangeEventHandler, ComponentType, FC, KeyboardEvent } from 'react'
import { memo, useCallback } from 'react'

import { useJsonViewerStore } from '../../stores/JsonViewerStore'
Expand Down Expand Up @@ -64,18 +64,31 @@ export function defineEasyType<Value> ({
}
}

const EasyTypeEditor: FC<EditorProps<string>> = ({ value, setValue }) => {
const EasyTypeEditor: FC<EditorProps<string>> = ({ value, setValue, abortEditing, commitEditing }) => {
const color = useJsonViewerStore(store => store.colorspace[colorKey])

const handleKeyDown = useCallback((event: KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault()
commitEditing(value)
}

if (event.key === 'Escape') {
event.preventDefault()
abortEditing()
}
}, [abortEditing, commitEditing, value])

const handleChange = useCallback<ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>>((event) => {
setValue(event.target.value)
}, [setValue])

return (
<InputBase
autoFocus
value={value}
onChange={
useCallback<ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>>(
(event) => {
setValue(event.target.value)
}, [setValue]
)
}
onChange={handleChange}
onKeyDown={handleKeyDown}
size='small'
multiline
sx={{
Expand Down
2 changes: 2 additions & 0 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export interface DataItemProps<ValueType = unknown> {
export type EditorProps<ValueType = unknown> = {
value: ValueType
setValue: Dispatch<ValueType>
abortEditing: () => void
commitEditing: (newValue: string) => void
}

/**
Expand Down

0 comments on commit 1a757e8

Please sign in to comment.