Skip to content

Commit f65c089

Browse files
committed
feat: add json editing features. (#3)
1 parent 295f179 commit f65c089

File tree

22 files changed

+649
-109
lines changed

22 files changed

+649
-109
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jobs:
7171
[![](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/@uiw/react-json-view@${{steps.create_tag.outputs.versionNumber}}/file/README.md)
7272
7373
Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/uiwjs/react-json-view/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html
74+
Or Doc Website: https://htmlpreview.github.io/?https://github.com/uiwjs/react-json-view/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html
7475
Comparing Changes: ${{ steps.changelog.outputs.compareurl }}
7576
7677
```bash

core/README.md

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const customTheme = {
126126
'--w-rjv-type-bigint-color': '#268bd2',
127127
'--w-rjv-type-boolean-color': '#559bd4',
128128
'--w-rjv-type-date-color': '#586e75',
129+
'--w-rjv-type-url-color': '#649bd8',
129130
'--w-rjv-type-null-color': '#d33682',
130131
'--w-rjv-type-nan-color': '#859900',
131132
'--w-rjv-type-undefined-color': '#586e75',
@@ -156,6 +157,7 @@ const object = {
156157
null: null,
157158
undefined,
158159
timer: 0,
160+
url: new URL('https://example.com'),
159161
date: new Date('Tue Sep 13 2022 14:07:44 GMT-0500 (Central Daylight Time)'),
160162
array: [19, 100.86, 'test', NaN, Infinity],
161163
nestedArray: [
@@ -188,6 +190,7 @@ const customTheme = {
188190
'--w-rjv-type-bigint-color': '#268bd2',
189191
'--w-rjv-type-boolean-color': '#559bd4',
190192
'--w-rjv-type-date-color': '#586e75',
193+
'--w-rjv-type-url-color': '#0969da',
191194
'--w-rjv-type-null-color': '#d33682',
192195
'--w-rjv-type-nan-color': '#859900',
193196
'--w-rjv-type-undefined-color': '#586e75',
@@ -240,6 +243,7 @@ export default function Demo() {
240243
)
241244
}
242245
```
246+
243247
## Render
244248

245249
```tsx mdx:preview
@@ -269,13 +273,13 @@ export default function Demo() {
269273
'--w-rjv-border-left-width': '0',
270274
}}
271275
components={{
272-
braces: () => <span />,
273-
ellipsis: () => <React.Fragment />,
274-
objectKey: ({ value, ...props}) => {
275-
if (props.children === '"integer"' && value > 40) {
276+
braces: () => <span />,
277+
ellipsis: () => <React.Fragment />,
278+
objectKey: ({ value, keyName, parentName, ...props}) => {
279+
if (keyName === 'integer' && typeof value === 'number' && value > 40) {
276280
return <del {...props} />
277281
}
278-
return <span {...props} />
282+
return <span {...props} />
279283
}
280284
}}
281285
/>
@@ -295,7 +299,7 @@ const object = {
295299
integer: 42,
296300
}
297301

298-
function value({ type, children, ...props }) {
302+
function value({ type, children, keyName, visible, ...props }) {
299303
if (type === 'string' && /\.(jpg)$/.test(children)) {
300304
return (
301305
<span {...props}>
@@ -348,14 +352,15 @@ export default function Demo() {
348352
import React from 'react';
349353
import JsonView from '@uiw/react-json-view';
350354

351-
function value({ type, children, value, ...props }) {
355+
function value({ type, children, visible, keyName, value, ...props }) {
352356
if (value instanceof URL) {
353357
return (
354358
<span {...props}>
355359
<a href={value.href} target="_blank" rel="noopener noreferrer">
356360
{children}
357361
</a>
358362
&nbsp;Open URL
363+
{visible && <del>Button</del>}
359364
</span>
360365
);
361366
}
@@ -378,13 +383,60 @@ export default function Demo() {
378383
}
379384
```
380385

386+
## Editor JSON
387+
388+
```tsx mdx:preview
389+
import React from 'react';
390+
import JsonViewEditor from '@uiw/react-json-view/editor';
391+
392+
const object = {
393+
string: 'Lorem ipsum dolor sit amet',
394+
integer: 42,
395+
float: 114.514,
396+
object: {
397+
'first-child': true,
398+
'second-child': false,
399+
'last-child': null,
400+
'child': {
401+
'first': true,
402+
'second': false,
403+
'last': null,
404+
},
405+
},
406+
nestedArray: [ [1, 2], [3, 4], { a: 1} ],
407+
}
408+
409+
const ObjectKey = ({ value, keyName, parentName, ...reset }) => {
410+
if (keyName === 'integer' && typeof value === 'number' && value > 40) {
411+
return <del {...reset} />
412+
}
413+
return <span {...reset} />
414+
};
415+
416+
export default function Demo() {
417+
return (
418+
<JsonViewEditor
419+
value={object}
420+
keyName="root"
421+
style={{
422+
'--w-rjv-background-color': '#ffffff',
423+
}}
424+
onEdit={(opts) => {
425+
console.log('opts:', opts)
426+
}}
427+
components={{
428+
objectKey: ObjectKey
429+
}}
430+
/>
431+
);
432+
}
433+
```
434+
381435
## Highlight Updates
382436

383437
```tsx mdx:preview
384438
import React, { useState, useEffect } from 'react';
385439
import JsonView from '@uiw/react-json-view';
386-
import { TriangleArrow } from '@uiw/react-json-view/triangle-arrow';
387-
import { TriangleSolidArrow } from '@uiw/react-json-view/triangle-solid-arrow';
388440

389441
const object = {
390442
string: 'Lorem ipsum dolor sit amet',

core/editor.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference types="react" />
2+
3+
declare module '@uiw/react-json-view/editor' {
4+
import { JsonViewProps } from '@uiw/react-json-view';
5+
type Option = {
6+
value: string;
7+
prevValue: string;
8+
keyName: string | number;
9+
};
10+
11+
export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T> {
12+
onEdit?: (option: Option) => void;
13+
}
14+
const JsonViewEditor: import("react").ForwardRefExoticComponent<Omit<JsonViewEditorProps<object>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
15+
export default JsonViewEditor;
16+
}

core/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
"types": "./cjs/index.d.ts",
1313
"require": "./cjs/index.js"
1414
},
15+
"./editor": {
16+
"import": "./esm/editor/index.js",
17+
"types": "./cjs/editor/index.d.ts",
18+
"require": "./cjs/editor/index.js"
19+
},
1520
"./light": {
1621
"import": "./esm/theme/light.js",
1722
"types": "./cjs/theme/light.d.ts",
@@ -45,6 +50,7 @@
4550
"react-dom": ">=18.0.0"
4651
},
4752
"files": [
53+
"editor.d.ts",
4854
"dark.d.ts",
4955
"light.d.ts",
5056
"triangle-arrow.d.ts",

core/src/comps/ellipsis.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { FC, PropsWithChildren } from 'react';
2+
3+
export interface EllipsisProps extends React.HTMLAttributes<HTMLSpanElement> {
4+
render?: (props: EllipsisProps) => JSX.Element;
5+
}
6+
export const Ellipsis: FC<PropsWithChildren<EllipsisProps>> = ({ style, render, ...props }) => {
7+
const styl = { cursor: 'pointer', ...style };
8+
const className = `w-rjv-ellipsis ${props.className || ''}`;
9+
if (render) return render({ style: styl, ...props, className });
10+
return (
11+
<span className={className} style={styl} {...props}>
12+
...
13+
</span>
14+
);
15+
};

core/src/comps/meta.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Label, LabelProps } from '../value';
2+
3+
export interface MetaProps extends LabelProps {
4+
isArray?: boolean;
5+
start?: boolean;
6+
render?: (props: Pick<MetaProps, 'start' | 'isArray' | 'className' | 'children'>) => JSX.Element;
7+
}
8+
9+
export function Meta(props: MetaProps) {
10+
const { isArray = false, start = false, className, render, ...reset } = props;
11+
const mark = isArray ? '[]' : '{}';
12+
const cls = `w-rjv-${isArray ? 'brackets' : 'curlybraces'}-${start ? 'start' : 'end'} ${className || ''}`;
13+
const color = `var(--w-rjv-${isArray ? 'brackets' : 'curlybraces'}-color, #236a7c)`;
14+
if (render)
15+
return render({
16+
isArray,
17+
className: cls,
18+
style: { color },
19+
children: start ? mark.charAt(0) : mark.charAt(1),
20+
...reset,
21+
});
22+
return (
23+
<Label color={color} className={cls} {...reset}>
24+
{start ? mark.charAt(0) : mark.charAt(1)}
25+
</Label>
26+
);
27+
}

core/src/copied.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function Copied<T>(props: CopiedProps<T>) {
3333
})
3434
.catch((error) => {})
3535
};
36-
const defalutStyle = { ...style, cursor: 'pointer', marginLeft: 5 } as React.CSSProperties;
36+
const defalutStyle = { ...style, cursor: 'pointer',verticalAlign: 'middle', marginLeft: 5 } as React.CSSProperties;
3737
const svgProps: React.SVGProps<SVGSVGElement> = {
3838
height: '1em',
3939
width: '1em',
@@ -46,14 +46,14 @@ export function Copied<T>(props: CopiedProps<T>) {
4646
if (render) return render({ ...props, ...svgProps });
4747
if (copied) {
4848
return (
49-
<svg viewBox="0 0 38 38" {...svgProps} fill="var(--w-rjv-copied-success-color, #28a745)">
50-
<path d="M27.5,35 L2.5,35 L2.5,12.5 L27.5,12.5 L27.5,15.2249049 C29.1403264,13.8627542 29.9736597,13.1778155 30,13.1700887 C30,11.9705278 30,10.0804982 30,7.5 C30,6.1 28.9,5 27.5,5 L20,5 C20,2.2 17.8,0 15,0 C12.2,0 10,2.2 10,5 L2.5,5 C1.1,5 0,6.1 0,7.5 L0,35 C0,36.4 1.1,37.5 2.5,37.5 L27.5,37.5 C28.9,37.5 30,36.4 30,35 L30,30 L27.5,30 L27.5,35 Z M7.5,7.5 L10,7.5 C10,7.5 12.5,6.4 12.5,5 C12.5,3.6 13.6,2.5 15,2.5 C16.4,2.5 17.5,3.6 17.5,5 C17.5,6.4 18.8,7.5 20,7.5 L22.5,7.5 C22.5,7.5 25,8.6 25,10 L5,10 C5,8.5 6.1,7.5 7.5,7.5 Z M5,27.5 L10,27.5 L10,25 L5,25 L5,27.5 Z M31.5589286,15 L35.1589286,18.6 L21.0160714,33 L12.5303571,24.2571429 L16.1303571,20.6571429 L21.0160714,25.5428571 L31.5589286,15 Z M12.5,30 L12.5,32.5 L5,32.5 L5,30 L12.5,30 Z M17.5,15 L5,15 L5,17.5 L17.5,17.5 L17.5,15 Z M10,20 L5,20 L5,22.5 L10,22.5 L10,20 Z"></path>
49+
<svg viewBox="0 0 32 36" {...svgProps} fill="var(--w-rjv-copied-success-color, #28a745)">
50+
<path d="M27.5,33 L2.5,33 L2.5,12.5 L27.5,12.5 L27.5,15.2249049 C29.1403264,13.8627542 29.9736597,13.1778155 30,13.1700887 C30,11.9705278 30,10.0804982 30,7.5 C30,6.1 28.9,5 27.5,5 L20,5 C20,2.2 17.8,0 15,0 C12.2,0 10,2.2 10,5 L2.5,5 C1.1,5 0,6.1 0,7.5 L0,33 C0,34.4 1.1,36 2.5,36 L27.5,36 C28.9,36 30,34.4 30,33 L30,26.1114493 L27.5,28.4926435 L27.5,33 Z M7.5,7.5 L10,7.5 C10,7.5 12.5,6.4 12.5,5 C12.5,3.6 13.6,2.5 15,2.5 C16.4,2.5 17.5,3.6 17.5,5 C17.5,6.4 18.8,7.5 20,7.5 L22.5,7.5 C22.5,7.5 25,8.6 25,10 L5,10 C5,8.5 6.1,7.5 7.5,7.5 Z M5,27.5 L10,27.5 L10,25 L5,25 L5,27.5 Z M28.5589286,16 L32,19.6 L21.0160714,30.5382252 L13.5303571,24.2571429 L17.1303571,20.6571429 L21.0160714,24.5428571 L28.5589286,16 Z M17.5,15 L5,15 L5,17.5 L17.5,17.5 L17.5,15 Z M10,20 L5,20 L5,22.5 L10,22.5 L10,20 Z"></path>
5151
</svg>
5252
);
5353
}
5454
return (
55-
<svg viewBox="0 0 38 38" {...svgProps}>
56-
<path d="M27.5,35 L2.5,35 L2.5,12.5 L27.5,12.5 L27.5,20 L30,20 L30,7.5 C30,6.1 28.9,5 27.5,5 L20,5 C20,2.2 17.8,0 15,0 C12.2,0 10,2.2 10,5 L2.5,5 C1.1,5 0,6.1 0,7.5 L0,35 C0,36.4 1.1,37.5 2.5,37.5 L27.5,37.5 C28.9,37.5 30,36.4 30,35 L30,30 L27.5,30 L27.5,35 Z M7.5,7.5 L10,7.5 C10,7.5 12.5,6.4 12.5,5 C12.5,3.6 13.6,2.5 15,2.5 C16.4,2.5 17.5,3.6 17.5,5 C17.5,6.4 18.8,7.5 20,7.5 L22.5,7.5 C22.5,7.5 25,8.6 25,10 L5,10 C5,8.5 6.1,7.5 7.5,7.5 Z M5,27.5 L10,27.5 L10,25 L5,25 L5,27.5 Z M22.5,22.5 L22.5,17.5 L12.5,25 L22.5,32.5 L22.5,27.5 L35,27.5 L35,22.5 L22.5,22.5 Z M5,32.5 L12.5,32.5 L12.5,30 L5,30 L5,32.5 Z M17.5,15 L5,15 L5,17.5 L17.5,17.5 L17.5,15 Z M10,20 L5,20 L5,22.5 L10,22.5 L10,20 Z"></path>
55+
<svg viewBox="0 0 32 36" {...svgProps}>
56+
<path d="M27.5,33 L2.5,33 L2.5,12.5 L27.5,12.5 L27.5,20 L30,20 L30,7.5 C30,6.1 28.9,5 27.5,5 L20,5 C20,2.2 17.8,0 15,0 C12.2,0 10,2.2 10,5 L2.5,5 C1.1,5 0,6.1 0,7.5 L0,33 C0,34.4 1.1,36 2.5,36 L27.5,36 C28.9,36 30,34.4 30,33 L30,29 L27.5,29 L27.5,33 Z M7.5,7.5 L10,7.5 C10,7.5 12.5,6.4 12.5,5 C12.5,3.6 13.6,2.5 15,2.5 C16.4,2.5 17.5,3.6 17.5,5 C17.5,6.4 18.8,7.5 20,7.5 L22.5,7.5 C22.5,7.5 25,8.6 25,10 L5,10 C5,8.5 6.1,7.5 7.5,7.5 Z M5,27.5 L10,27.5 L10,25 L5,25 L5,27.5 Z M22.5,21.5 L22.5,16.5 L12.5,24 L22.5,31.5 L22.5,26.5 L32,26.5 L32,21.5 L22.5,21.5 Z M17.5,15 L5,15 L5,17.5 L17.5,17.5 L17.5,15 Z M10,20 L5,20 L5,22.5 L10,22.5 L10,20 Z"></path>
5757
</svg>
5858
);
5959
}

core/src/editor/icon/edit.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { CSSProperties } from 'react';
2+
3+
export interface EditIconProps extends React.SVGAttributes<SVGElement> {}
4+
export const EditIcon = (props: EditIconProps) => {
5+
const { style } = props
6+
const defaultStyle: CSSProperties = {
7+
verticalAlign: 'middle',
8+
display: 'inline-block',
9+
color: 'var(--w-rjv-edit-color, currentColor)',
10+
cursor: 'pointer',
11+
marginLeft: 5,
12+
height: '1em',
13+
width: '1em',
14+
}
15+
return (
16+
<svg
17+
viewBox="0 0 26 26"
18+
fill="currentColor"
19+
{...props}
20+
style={{ ...style, ...defaultStyle}}
21+
>
22+
<path d="M18,0 L7.74666667,0 C2.89333333,0 0,2.89333333 0,7.74666667 L0,17.9066667 C0,22.7733333 2.89333333,26 7.74666667,26 L18,26 C22.8533333,26 26.0001422,22.7733333 26.0001422,17.92 L26.0001422,7.74666667 C26.0133333,2.89333333 22.8533333,0 18,0 Z M11.9333333,20.68 C11.5466667,21.0666667 10.8133333,21.44 10.28,21.52 L7,21.9866667 C6.88,22 6.76,22.0133333 6.64,22.0133333 C6.09333333,22.0133333 5.58666667,21.8266667 5.22666667,21.4666667 C4.78666667,21.0266667 4.6,20.3866667 4.70666667,19.68 L5.17333333,16.4 C5.25333333,15.8533333 5.61333333,15.1333333 6.01333333,14.7466667 L11.96,8.8 C12.1719034,9.3972484 12.4397602,9.97314063 12.76,10.52 C12.8933333,10.7466667 13.04,10.96 13.16,11.12 C13.3066667,11.3466667 13.48,11.56 13.5866667,11.68 C13.6533333,11.7733333 13.7066667,11.84 13.7333333,11.8666667 C14.0666667,12.2666667 14.4533333,12.64 14.7866667,12.92 C14.88,13.0133333 14.9333333,13.0666667 14.96,13.08 C15.16,13.24 15.36,13.4 15.5333333,13.52 C15.7466667,13.68 15.96,13.8266667 16.1866667,13.9466667 C16.4533333,14.1066667 16.7466667,14.2533333 17.04,14.4 C17.3466667,14.5333333 17.6266667,14.6533333 17.9066667,14.7466667 L11.9333333,20.68 L11.9333333,20.68 Z M20.4933333,12.12 L19.2666667,13.36 C19.1887174,13.4373865 19.0831716,13.4805643 18.9733333,13.4800055 C18.9333333,13.4800055 18.88,13.4800055 18.8533333,13.4666667 C16.1309823,12.6775025 14.0024975,10.5490177 13.2133333,7.82666667 C13.1733333,7.68 13.2133333,7.52 13.32,7.42666667 L14.56,6.18666667 C16.5866667,4.16 18.52,4.2 20.5066667,6.18666667 C21.52,7.2 22.0133333,8.17333333 22.0133333,9.18666667 C22,10.1466667 21.5066667,11.1066667 20.4933333,12.12 L20.4933333,12.12 Z" />
23+
</svg>
24+
)
25+
}

core/src/editor/index.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { forwardRef } from 'react'
2+
import JsonView, { JsonViewProps } from '../';
3+
import { ObjectKey } from './objectKey';
4+
import { ReValue } from './value';
5+
6+
type Option = {
7+
value: unknown;
8+
oldValue: unknown;
9+
keyName?: string | number;
10+
parentName?: string | number;
11+
type?: 'value' | 'key'
12+
}
13+
14+
export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T> {
15+
/** Callback when value edit functionality */
16+
onEdit?: (option: Option) => void;
17+
/** Whether enable edit feature. @default true */
18+
editable?: boolean;
19+
}
20+
21+
const JsonViewEditor = forwardRef<HTMLDivElement, JsonViewEditorProps<object>>((props, ref) => {
22+
const { onEdit, components, editable = true, displayDataTypes = true, ...reset } = props;
23+
const comps: JsonViewEditorProps<object>['components'] = {
24+
...components,
25+
}
26+
if (editable) {
27+
comps.objectKey = (reprops) => <ObjectKey {...reprops} onEdit={onEdit} render={components?.objectKey} />;
28+
comps.value = (reprops) => {
29+
return <ReValue {...reprops} displayDataTypes={displayDataTypes} onEdit={onEdit} />
30+
};
31+
}
32+
return (
33+
<JsonView {...reset} displayDataTypes={false} components={{...comps}} ref={ref} />
34+
)
35+
});
36+
37+
export default JsonViewEditor;

0 commit comments

Comments
 (0)