Skip to content

Commit 04b47a4

Browse files
committed
feat(editor): add onAdd props. (#5)
1 parent f0a0f85 commit 04b47a4

File tree

14 files changed

+167
-64
lines changed

14 files changed

+167
-64
lines changed

core/README.md

Lines changed: 26 additions & 12 deletions
Large diffs are not rendered by default.

core/editor.d.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,29 @@
22

33
declare module '@uiw/react-json-view/editor' {
44
import { JsonViewProps } from '@uiw/react-json-view';
5+
import type { CountInfoExtraProps } from '@uiw/react-json-view/cjs/editor/countInfoExtra';
56
type Option = {
67
value: string;
78
prevValue: string;
89
keyName: string | number;
910
};
1011

1112
export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T> {
12-
onEdit?: (option: Option) => void;
13+
/** Callback when value edit functionality */
14+
onEdit?: (option: {
15+
value: unknown;
16+
oldValue: unknown;
17+
keyName?: string | number;
18+
parentName?: string | number;
19+
type?: 'value' | 'key';
20+
}) => void;
21+
/**
22+
* When a callback function is passed in, add functionality is enabled. The callback is invoked before additions are completed.
23+
* @returns {boolean} Returning false from onAdd will prevent the change from being made.
24+
*/
25+
onAdd?: CountInfoExtraProps<T>['onAdd'];
26+
/** Whether enable edit feature. @default true */
27+
editable?: boolean;
1328
}
1429
const JsonViewEditor: import("react").ForwardRefExoticComponent<Omit<JsonViewEditorProps<object>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
1530
export default JsonViewEditor;

core/src/editor/countInfoExtra.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { AddIcon } from './icon/add';
2+
import type { CountInfoProps } from '../';
3+
4+
export interface CountInfoExtraProps<T> extends CountInfoProps {
5+
editable: boolean;
6+
showTools: boolean;
7+
value: T;
8+
setValue: React.Dispatch<React.SetStateAction<T>>
9+
/**
10+
* When a callback function is passed in, add functionality is enabled. The callback is invoked before additions are completed.
11+
* @returns {boolean} Returning false from onAdd will prevent the change from being made.
12+
*/
13+
onAdd?: (keyOrValue: string, newValue: T, value: T, isAdd: boolean) => boolean;
14+
}
15+
16+
export function CountInfoExtra<T extends object>(props: CountInfoExtraProps<T>) {
17+
const { visible, showTools, value, setValue, onAdd } = props;
18+
if (!visible || !showTools) return null;
19+
const click = async (event: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
20+
event.stopPropagation();
21+
const keyOrValue = 'AddKeyOrValue';
22+
const isArray = Array.isArray(value);
23+
const isAdd = isArray ? true : !(keyOrValue in value);
24+
const result = isArray ? [...value, keyOrValue] : { ...value, [keyOrValue]: undefined };
25+
if (onAdd) {
26+
const maybeAdd = await onAdd(keyOrValue, result as T, props.value, isAdd);
27+
if (maybeAdd) {
28+
setValue(result as T);
29+
}
30+
}
31+
}
32+
const svgProps: React.SVGProps<SVGSVGElement> = {
33+
onClick: click,
34+
}
35+
return (
36+
<AddIcon {...svgProps} />
37+
);
38+
}

core/src/editor/icon/add.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { CSSProperties } from 'react';
2+
3+
export interface AddIconProps extends React.SVGAttributes<SVGElement> {}
4+
export const AddIcon = (props: AddIconProps) => {
5+
const { style } = props
6+
const defaultStyle: CSSProperties = {
7+
verticalAlign: 'middle',
8+
display: 'inline-block',
9+
cursor: 'pointer',
10+
marginLeft: 5,
11+
height: '1em',
12+
width: '1em',
13+
}
14+
return (
15+
<svg viewBox="0 0 20 20" fill="var(--w-rjv-edit-color, currentColor)" {...props} style={{ ...style, ...defaultStyle}}>
16+
<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" />
17+
</svg>
18+
);
19+
}

core/src/editor/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { forwardRef } from 'react'
2-
import JsonView, { JsonViewProps } from '../';
2+
import JsonView from '../';
3+
import type { JsonViewProps } from '../';
34
import { ObjectKey } from './objectKey';
45
import { ReValue } from './value';
6+
import { CountInfoExtra } from './countInfoExtra';
7+
import type { CountInfoExtraProps } from './countInfoExtra';
58

69
export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T> {
710
/** Callback when value edit functionality */
@@ -12,14 +15,20 @@ export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T>
1215
parentName?: string | number;
1316
type?: 'value' | 'key';
1417
}) => void;
18+
/**
19+
* When a callback function is passed in, add functionality is enabled. The callback is invoked before additions are completed.
20+
* @returns {boolean} Returning false from onAdd will prevent the change from being made.
21+
*/
22+
onAdd?: CountInfoExtraProps<T>['onAdd'];
1523
/** Whether enable edit feature. @default true */
1624
editable?: boolean;
1725
}
1826

1927
const JsonViewEditor = forwardRef<HTMLDivElement, JsonViewEditorProps<object>>((props, ref) => {
20-
const { onEdit, components, editable = true, displayDataTypes = true, ...reset } = props;
28+
const { onEdit, components, editable = true, displayDataTypes = true, onAdd, ...reset } = props;
2129
const comps: JsonViewEditorProps<object>['components'] = {
2230
...components,
31+
countInfoExtra: (reprops) => <CountInfoExtra {...reprops} editable={editable} onAdd={onAdd} />,
2332
objectKey: (reprops) => <ObjectKey {...reprops} editableValue={editable} onEdit={onEdit} render={components?.objectKey} />,
2433
value: (reprops) => {
2534
return <ReValue {...reprops} editableValue={editable} displayDataTypes={displayDataTypes} onEdit={onEdit} />

core/src/editor/objectKey.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { FC, useEffect, useRef, useState } from 'react';
2-
import { SemicolonProps, useHighlight } from '../semicolon';
2+
import { useHighlight } from '../semicolon';
3+
import type { SemicolonProps } from '../semicolon';
34
import { Label } from '../value';
4-
import { JsonViewEditorProps } from './';
5+
import type { JsonViewEditorProps } from './';
56

67
export interface ObjectKeyProps<T extends object> extends SemicolonProps {
78
onEdit?: JsonViewEditorProps<T>['onEdit'];

core/src/editor/value.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { FC, Fragment, PropsWithChildren, useEffect, useRef, useState } from 'react';
1+
import type { FC } from 'react';
2+
import { Fragment, PropsWithChildren, useEffect, useRef, useState } from 'react';
23
import type { TypeProps } from '../value';
34
import { getValueString, isFloat, Type, typeMap } from '../value';
45
import { EditIcon } from './icon/edit';
5-
import { JsonViewEditorProps } from './';
6+
import type { JsonViewEditorProps } from './';
67

78
const Quotes: FC<PropsWithChildren<React.HTMLAttributes<HTMLSpanElement> & { quotes?: JsonViewEditorProps<object>['quotes']; show?: boolean; }>> = ({ show, style, quotes }) => {
89
if (!quotes || !show) return;

core/src/index.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import React, { useId } from 'react';
22
import { forwardRef } from 'react';
33
import { RooNode } from './node';
4-
import { SemicolonProps } from './semicolon';
5-
import { ValueViewProps } from './value';
6-
import { CopiedProps } from './copied';
7-
import { EllipsisProps } from './comps/ellipsis'
8-
import { MetaProps } from './comps/meta'
4+
import type { SemicolonProps } from './semicolon';
5+
import type { ValueViewProps } from './value';
6+
import type { CopiedProps } from './copied';
7+
import type { EllipsisProps } from './comps/ellipsis';
8+
import type { MetaProps } from './comps/meta';
9+
import type { CountInfoExtraProps } from './editor/countInfoExtra';
910

1011
export * from './node';
1112
export * from './value';
13+
14+
export interface CountInfoProps {
15+
count: number;
16+
level: number;
17+
visible: boolean;
18+
}
19+
1220
export interface JsonViewProps<T extends object>
1321
extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
1422
/** This property contains your input JSON */
@@ -43,7 +51,8 @@ export interface JsonViewProps<T extends object>
4351
objectKey?: SemicolonProps['render'];
4452
value?: ValueViewProps<T>['renderValue'];
4553
copied?: CopiedProps<T>['render'];
46-
countInfo?: (props: { count: number; level: number; visible: boolean }) => JSX.Element;
54+
countInfo?: (props: CountInfoProps) => JSX.Element;
55+
countInfoExtra?: (props: Omit<CountInfoExtraProps<T>, 'editable'>) => JSX.Element;
4756
};
4857
}
4958

core/src/node.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { FC, Fragment, PropsWithChildren, useId, cloneElement, useState } from 'react';
1+
import { FC, Fragment, PropsWithChildren, useId, cloneElement, useState, useEffect } from 'react';
22
import { ValueView, ValueViewProps, Colon, Label, LabelProps, Line, typeMap } from './value';
33
import { TriangleArrow } from './arrow/TriangleArrow';
44
import { useExpandsStatus, store } from './store';
55
import { JsonViewProps } from './';
66
import { Semicolon } from './semicolon';
7-
import { Tools } from './tools';
7+
import { Copied } from './copied';
88
import { Ellipsis } from './comps/ellipsis';
99
import { Meta } from './comps/meta';
1010

@@ -86,10 +86,14 @@ export function RooNode<T extends object>(props: RooNodeProps<T>) {
8686
eventProps.onMouseEnter = () => setShowTools(true);
8787
eventProps.onMouseLeave = () => setShowTools(false);
8888
}
89-
const nameKeys = (isArray ? Object.keys(value).map(m => Number(m)) : Object.keys(value)) as (keyof typeof value)[];
89+
const [valueData, setValueData] = useState<T>(value as T);
90+
91+
useEffect(() => setValueData(value as T), [value]);
92+
93+
const nameKeys = (isArray ? Object.keys(valueData).map(m => Number(m)) : Object.keys(valueData)) as (keyof typeof valueData)[];
9094

9195
// object
92-
let entries: [key: string | number, value: unknown][] = isArray ? Object.entries(value).map(m => [Number(m[0]), m[1]]) : Object.entries(value);
96+
let entries: [key: string | number, value: unknown][] = isArray ? Object.entries(valueData).map(m => [Number(m[0]), m[1]]) : Object.entries(valueData);
9397
if (objectSortKeys) {
9498
entries = objectSortKeys === true
9599
? entries.sort(([a], [b]) => typeof a === 'string' && typeof b === 'string' ? a.localeCompare(b) : 0)
@@ -106,7 +110,7 @@ export function RooNode<T extends object>(props: RooNodeProps<T>) {
106110
{(typeof keyName === 'string' || typeof keyName === 'number') && (
107111
<Fragment>
108112
<Semicolon
109-
value={value}
113+
value={valueData}
110114
quotes={quotes}
111115
data-keys={keyid}
112116
render={components.objectKey}
@@ -121,13 +125,15 @@ export function RooNode<T extends object>(props: RooNodeProps<T>) {
121125
{!expand && <Ellipsis render={components.ellipsis} count={nameKeys.length} level={level} />}
122126
{!expand && <Meta isArray={isArray} level={level} render={components.braces} />}
123127
{displayObjectSize && countInfo}
124-
<Tools
125-
value={value}
126-
enableClipboard={enableClipboard}
127-
onCopied={onCopied}
128-
components={components}
129-
showTools={showTools}
130-
/>
128+
{components.countInfoExtra && components.countInfoExtra({
129+
count: nameKeys.length,
130+
level,
131+
showTools,
132+
visible: expand,
133+
value: valueData,
134+
setValue: setValueData,
135+
})}
136+
{enableClipboard && <Copied show={showTools} text={valueData as T} onCopied={onCopied} render={components?.copied} />}
131137
</Line>
132138
{expand && (
133139
<Line className="w-rjv-content" style={{ borderLeft: 'var(--w-rjv-border-left-width, 1px) var(--w-rjv-line-style, solid) var(--w-rjv-line-color, #ebebeb)', marginLeft: 6 }}>

core/src/semicolon.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { FC, PropsWithChildren, useMemo, useRef, useEffect } from 'react';
2-
import { Label, LabelProps } from './value';
3-
import { JsonViewProps } from './';
1+
import type { FC, PropsWithChildren } from 'react';
2+
import { useMemo, useRef, useEffect } from 'react';
3+
import { Label } from './value';
4+
import type { LabelProps } from './value';
5+
import type { JsonViewProps } from './';
46

57
export function usePrevious<T>(value: T) {
68
const ref = useRef<T>();

0 commit comments

Comments
 (0)