Skip to content

Commit 31eaf9d

Browse files
committed
refactor: enhanced onEdit props. (#5)
1 parent 0cd05c2 commit 31eaf9d

File tree

9 files changed

+159
-36
lines changed

9 files changed

+159
-36
lines changed

core/README.md

+21-5
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ const customTheme = {
123123
'--w-rjv-line-color': '#323232',
124124
'--w-rjv-arrow-color': 'var(--w-rjv-color)',
125125
'--w-rjv-edit-color': 'var(--w-rjv-color)',
126+
'--w-rjv-add-color': 'var(--w-rjv-color)',
126127
'--w-rjv-info-color': '#656565',
127128
'--w-rjv-update-color': '#ebcb8b',
128129
'--w-rjv-copied-color': '#9cdcfe',
@@ -187,7 +188,8 @@ const customTheme = {
187188
'--w-rjv-background-color': '#1e1e1e',
188189
'--w-rjv-line-color': '#323232',
189190
'--w-rjv-arrow-color': '#9cdcfe',
190-
'--w-rjv-edit-color': '#9cdcfe',
191+
'--w-rjv-edit-color': '#0184a6',
192+
'--w-rjv-add-color': '#0184a6',
191193
'--w-rjv-info-color': '#656565',
192194
'--w-rjv-update-color': '#ebcb8b',
193195
'--w-rjv-copied-color': '#0184a6',
@@ -237,7 +239,18 @@ export default function Demo() {
237239
<input type="checkbox" checked={editable} onChange={changeEditable} /> Editable
238240
</label>
239241
<div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-start' }}>
240-
<JsonView editable={editable} value={src} keyName="root" style={{ flex: 1, overflow: 'auto', ...theme }} />
242+
<JsonView
243+
editable={editable}
244+
value={src}
245+
keyName="root"
246+
onAdd={(keyOrValue, newValue, value, isAdd) => {
247+
return isAdd;
248+
}}
249+
onEdit={(opts) => {
250+
return true;
251+
}}
252+
style={{ flex: 1, overflow: 'auto', ...theme }}
253+
/>
241254
<div>
242255
<Colorful color={hex} onChange={onChange} />
243256
<div style={{ display: 'flex', gap: '0.4rem', flexDirection: 'column' }}>
@@ -247,7 +260,7 @@ export default function Demo() {
247260
setHex(customTheme[varname]);
248261
};
249262
const active = cssvar === varname ? '#a8a8a8' : '';
250-
return <button key={idx} style={{ background: active }} onClick={click}>{varname}</button>
263+
return <button key={idx} style={{ background: active, border: 0,boxShadow: 'inset 0px 0px 1px #000' }} onClick={click}>{varname}</button>
251264
})}
252265
</div>
253266
</div>
@@ -730,14 +743,17 @@ export interface CountInfoProps {
730743
import { JsonViewProps } from '@uiw/react-json-view';
731744
import type { CountInfoExtraProps } from '@uiw/react-json-view/cjs/editor/countInfoExtra';
732745
export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T> {
733-
/** Callback when value edit functionality */
746+
/**
747+
* When a callback function is passed in, edit functionality is enabled. The callback is invoked before edits are completed.
748+
* @returns {boolean} Returning false from onEdit will prevent the change from being made.
749+
*/
734750
onEdit?: (option: {
735751
value: unknown;
736752
oldValue: unknown;
737753
keyName?: string | number;
738754
parentName?: string | number;
739755
type?: 'value' | 'key';
740-
}) => void;
756+
}) => boolean;
741757
/**
742758
* When a callback function is passed in, add functionality is enabled. The callback is invoked before additions are completed.
743759
* @returns {boolean} Returning false from onAdd will prevent the change from being made.
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import renderer from 'react-test-renderer';
2+
import { CountInfoExtra } from './countInfoExtra';
3+
4+
5+
it('renders <CountInfoExtra /> test case', () => {
6+
const component = renderer.create(<CountInfoExtra editable showTools value={{ a: 123 }} />);
7+
let tree = component.toJSON();
8+
expect(tree).toBeNull();
9+
});
10+
it('renders <CountInfoExtra /> editable=false test case', () => {
11+
const component = renderer.create(<CountInfoExtra editable={false} visible showTools value={{ a: 123 }} />);
12+
let tree = component.toJSON();
13+
expect(tree).toBeNull();
14+
});
15+
16+
17+
it('renders <CountInfoExtra /> onAdd test case', () => {
18+
const component = renderer.create(<CountInfoExtra editable visible showTools value={{ a: 123 }} onAdd={() => true}/>);
19+
let tree = component.toJSON();
20+
expect(tree).toHaveProperty('type');
21+
expect(tree).toHaveProperty('props');
22+
expect(tree).toHaveProperty('children');
23+
expect(tree).toHaveProperty('type', 'svg');
24+
expect(tree).toHaveProperty('props.fill', 'var(--w-rjv-add-color, currentColor)')
25+
expect(tree).toHaveProperty('props.viewBox', '0 0 20 20')
26+
expect(tree).toHaveProperty('props.style', {
27+
verticalAlign: 'middle',
28+
display: 'inline-block',
29+
cursor: 'pointer',
30+
marginLeft: 5,
31+
height: '1em',
32+
width: '1em',
33+
});
34+
});

core/src/editor/countInfoExtra.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { AddIcon } from './icon/add';
22
import type { CountInfoProps } from '../';
33

4-
export interface CountInfoExtraProps<T> extends CountInfoProps {
4+
export interface CountInfoExtraProps<T> extends Partial<CountInfoProps> {
55
editable: boolean;
66
showTools: boolean;
77
value: T;
8-
setValue: React.Dispatch<React.SetStateAction<T>>
8+
setValue?: React.Dispatch<React.SetStateAction<T>>
99
/**
1010
* When a callback function is passed in, add functionality is enabled. The callback is invoked before additions are completed.
1111
* @returns {boolean} Returning false from onAdd will prevent the change from being made.
@@ -14,7 +14,7 @@ export interface CountInfoExtraProps<T> extends CountInfoProps {
1414
}
1515

1616
export function CountInfoExtra<T extends object>(props: CountInfoExtraProps<T>) {
17-
const { visible, showTools, value, setValue, onAdd } = props;
17+
const { visible, showTools, editable, value, setValue, onAdd } = props;
1818
if (!visible || !showTools) return null;
1919
const click = async (event: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
2020
event.stopPropagation();
@@ -25,13 +25,14 @@ export function CountInfoExtra<T extends object>(props: CountInfoExtraProps<T>)
2525
if (onAdd) {
2626
const maybeAdd = await onAdd(keyOrValue, result as T, props.value, isAdd);
2727
if (maybeAdd) {
28-
setValue(result as T);
28+
setValue!(result as T);
2929
}
3030
}
3131
}
3232
const svgProps: React.SVGProps<SVGSVGElement> = {
3333
onClick: click,
3434
}
35+
if (!editable || !onAdd) return;
3536
return (
3637
<AddIcon {...svgProps} />
3738
);

core/src/editor/icon/add.test.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import renderer from 'react-test-renderer';
2+
import { AddIcon } from './add';
3+
4+
5+
it('renders <AddIcon /> test case', () => {
6+
const component = renderer.create(
7+
<AddIcon />,
8+
);
9+
let tree = component.toJSON();
10+
expect(tree).toHaveProperty('type');
11+
expect(tree).toHaveProperty('props');
12+
expect(tree).toHaveProperty('children');
13+
expect(tree).toHaveProperty('type', 'svg');
14+
expect(tree).toHaveProperty('props', {
15+
viewBox: '0 0 20 20',
16+
fill: 'var(--w-rjv-add-color, currentColor)',
17+
style: {
18+
verticalAlign: 'middle',
19+
display: 'inline-block',
20+
cursor: 'pointer',
21+
marginLeft: 5,
22+
height: '1em',
23+
width: '1em',
24+
}
25+
});
26+
});

core/src/editor/icon/add.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const AddIcon = (props: AddIconProps) => {
1212
width: '1em',
1313
}
1414
return (
15-
<svg viewBox="0 0 20 20" fill="var(--w-rjv-edit-color, currentColor)" {...props} style={{ ...style, ...defaultStyle}}>
15+
<svg viewBox="0 0 20 20" fill="var(--w-rjv-add-color, currentColor)" {...props} style={{ ...style, ...defaultStyle}}>
1616
<path d="M14.1970498,0 L5.81288651,0 C2.17107809,0 0,2.17 0,5.81 L0,14.18 C0,17.83 2.17107809,20 5.81288651,20 L14.1870449,20 C17.8288533,20 19.9999658,17.83 19.9999658,14.19 L19.9999658,5.81 C20.0099363,2.17 17.8388583,0 14.1970498,0 Z M16.0079491,10.75 L10.7553408,10.75 L10.7553408,16 C10.7553408,16.41 10.4151719,16.75 10.0049682,16.75 C9.59476448,16.75 9.25459556,16.41 9.25459556,16 L9.25459556,10.75 L4.00198727,10.75 C3.59178357,10.75 3.25161466,10.41 3.25161466,10 C3.25161466,9.59 3.59178357,9.25 4.00198727,9.25 L9.25459556,9.25 L9.25459556,4 C9.25459556,3.59 9.59476448,3.25 10.0049682,3.25 C10.4151719,3.25 10.7553408,3.59 10.7553408,4 L10.7553408,9.25 L16.0079491,9.25 C16.4181528,9.25 16.7583217,9.59 16.7583217,10 C16.7583217,10.41 16.4181528,10.75 16.0079491,10.75 Z" />
1717
</svg>
1818
);

core/src/editor/index.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ import { CountInfoExtra } from './countInfoExtra';
77
import type { CountInfoExtraProps } from './countInfoExtra';
88

99
export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T> {
10-
/** Callback when value edit functionality */
10+
/**
11+
* When a callback function is passed in, edit functionality is enabled. The callback is invoked before edits are completed.
12+
* @returns {boolean} Returning false from onEdit will prevent the change from being made.
13+
*/
1114
onEdit?: (option: {
1215
value: unknown;
1316
oldValue: unknown;
1417
keyName?: string | number;
1518
parentName?: string | number;
1619
type?: 'value' | 'key';
17-
}) => void;
20+
}) => boolean;
1821
/**
1922
* When a callback function is passed in, add functionality is enabled. The callback is invoked before additions are completed.
2023
* @returns {boolean} Returning false from onAdd will prevent the change from being made.

core/src/editor/value.test.tsx

+39-2
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ it('renders <ReValue /> value="test" test case', () => {
5656
});
5757

5858

59-
it('renders <ReValue /> editable test case', () => {
59+
it('renders <ReValue /> editable & onEdit test case', () => {
6060
const component = renderer.create(
61-
<ReValue type="string" displayDataTypes value="test" visible={true} editableValue={true} />,
61+
<ReValue type="string" displayDataTypes value="test" visible={true} editableValue={true} onEdit={() => true} />,
6262
);
6363
const tree = component.toJSON();
6464
expect(Array.isArray(tree)).toBe(true);
@@ -81,4 +81,41 @@ it('renders <ReValue /> editable test case', () => {
8181
expect(child1.children).toHaveLength(1);
8282
expect(child1).toHaveProperty('children[0].type', 'path');
8383
}
84+
});
85+
86+
it('renders <ReValue /> onEdit test case', () => {
87+
const component = renderer.create(
88+
<ReValue type="string" displayDataTypes value="test" visible={true} editableValue={true} />,
89+
);
90+
const tree = component.toJSON();
91+
expect(Array.isArray(tree)).toBe(true);
92+
if (Array.isArray(tree)) {
93+
expect(tree).toHaveLength(2);
94+
const child1 = tree[1];
95+
expect(child1).toHaveProperty('type');
96+
expect(child1).toHaveProperty('props');
97+
expect(child1).toHaveProperty('type', 'span');
98+
expect(tree[2]).toBeUndefined();
99+
}
100+
});
101+
102+
it('renders <ReValue /> quotes test case', () => {
103+
const component = renderer.create(
104+
<ReValue type="string" displayDataTypes quotes="'" value="test" visible={true} editableValue={true} />,
105+
);
106+
const tree = component.toJSON();
107+
expect(Array.isArray(tree)).toBe(true);
108+
if (Array.isArray(tree)) {
109+
expect(tree).toHaveLength(4);
110+
const child1 = tree[1];
111+
expect(child1).toHaveProperty('type');
112+
expect(child1).toHaveProperty('props');
113+
expect(child1).toHaveProperty('type', 'span');
114+
expect(tree[2]).toHaveProperty('type', 'span');
115+
expect(tree[2]).toHaveProperty('props.onBlur');
116+
expect(tree[2]).toHaveProperty('props.onKeyDown');
117+
expect(tree[2]).toHaveProperty('props.spellCheck', false);
118+
expect(tree[2]).toHaveProperty('props.style', { color: 'var(--w-rjv-type-string-color, #cb4b16)' });
119+
expect(tree[2]).toHaveProperty('props.data-value', '"test"');
120+
}
84121
});

core/src/editor/value.tsx

+27-22
Original file line numberDiff line numberDiff line change
@@ -46,48 +46,55 @@ export function ReValue<T extends object>(props: ReValueProps<T>) {
4646
$edit.current!.contentEditable = 'false';
4747
}
4848
}
49-
const blur = () => {
49+
const blur = async () => {
5050
if (!editableValue) return;
5151
setEditable(false);
5252
if ($edit.current) {
5353
$edit.current.contentEditable = 'false';
5454
let text: unknown = $edit.current.innerText as string;
55+
let typeStr = curentType;
5556
if (curentType === 'number' || curentType === 'float') {
5657
text = Number(text);
57-
setCurentType(isFloat(text as number) ? 'float' : 'number');
58+
typeStr = isFloat(text as number) ? 'float' : 'number';
5859
}
5960
if (curentType === 'url' && typeof text === 'string') {
6061
text = new URL(text);
62+
typeStr = 'url';
6163
}
6264
if (Number.isNaN(text)) {
63-
setCurentType('number');
65+
typeStr = 'number';
6466
}
6567
if (typeof text === 'string' && /^(true|false)$/ig.test(text)) {
6668
text = /^(true)$/ig.test(text) ? true : false;
67-
setCurentType('boolean');
69+
typeStr = 'boolean';
6870
} else if (typeof text === 'string' && /^[\d]+n$/ig.test(text)) {
6971
text = BigInt(text.replace(/n$/ig, ''));
70-
setCurentType('bigint');
72+
typeStr = 'bigint';
7173
} else if (typeof text === 'string' && /^(null)$/ig.test(text)) {
72-
text = null
73-
setCurentType('null');
74+
text = null;
75+
typeStr = 'null';
7476
} else if (typeof text === 'string' && /^(undefined)$/ig.test(text)) {
7577
text = undefined;
76-
setCurentType('undefined');
78+
typeStr = 'undefined';
7779
} else if (typeof text === 'string') {
7880
try {
79-
const dt = new Date(text)
80-
if (dt.toString() !== 'Invalid Date') {
81+
if(text && text.length > 10 && !isNaN(Date.parse(text))){
82+
const dt = new Date(text);
8183
text = dt;
82-
setCurentType('date');
84+
typeStr = 'date';
8385
}
84-
85-
} catch (error) {
86-
86+
} catch (error) {}
87+
}
88+
if (onEdit) {
89+
const result = await onEdit({ type: 'value', value: text, oldValue: curentChild });
90+
if (result) {
91+
setCurentType(typeStr);
92+
setCurentChild(text);
93+
} else {
94+
const { content: oldChildStr } = getValueString(curentChild);
95+
$edit.current.innerHTML = String(oldChildStr);
8796
}
8897
}
89-
setCurentChild(text);
90-
onEdit && onEdit({ type: 'value', value: text, oldValue: curentChild })
9198
}
9299
}
93100
const defaultStyle = { minWidth: 34, minHeight: 18, paddingInline: 3, display: 'inline-block' } as React.CSSProperties;
@@ -107,12 +114,10 @@ export function ReValue<T extends object>(props: ReValueProps<T>) {
107114
return (
108115
<Fragment>
109116
{displayDataTypes && typeView}
110-
<Fragment>
111-
<Quotes style={style} quotes={quotes} show={typeStr === 'string'} />
112-
<span {...spanProps} ref={$edit} data-value={childStr}>{typeof curentChild === 'string' ? curentChild : childStr}</span>
113-
<Quotes style={style} quotes={quotes} show={typeStr === 'string'} />
114-
</Fragment>
115-
{visible && editableValue && <EditIcon onClick={click} />}
117+
<Quotes style={style} quotes={quotes} show={typeStr === 'string'} />
118+
<span {...spanProps} ref={$edit} data-value={childStr}>{typeof curentChild === 'string' ? curentChild : childStr}</span>
119+
<Quotes style={style} quotes={quotes} show={typeStr === 'string'} />
120+
{visible && editableValue && onEdit && <EditIcon onClick={click} />}
116121
</Fragment>
117122
);
118123

www/src/example/editor.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export function ExampleEditor() {
8080
value={src}
8181
onEdit={(opts) => {
8282
console.log('opts:', opts)
83+
return true;
8384
}}
8485
onAdd={(keyOrValue, newValue, value, isAdd) => {
8586
console.log('keyOrValue:', keyOrValue)

0 commit comments

Comments
 (0)