From d206c35238311789b420226ac99df171d5c8ba38 Mon Sep 17 00:00:00 2001 From: jaywcjlove <398188662@qq.com> Date: Tue, 1 Aug 2023 15:57:24 +0800 Subject: [PATCH] fix: fix `onDelete` props issue. (#6) --- core/README.md | 60 +++++------------- core/src/editor/countInfoExtra.tsx | 40 ++++++++---- core/src/editor/value.tsx | 13 ++-- core/src/index.tsx | 18 +++++- core/src/node.tsx | 99 +++++++++++++++++++++--------- core/src/value.tsx | 42 ++++++------- tsconfig.json | 2 +- www/src/App.tsx | 2 +- www/src/example/default.tsx | 44 ++++++------- www/src/example/editor.tsx | 45 +++++++------- 10 files changed, 200 insertions(+), 165 deletions(-) diff --git a/core/README.md b/core/README.md index c5a97976..b18c2317 100644 --- a/core/README.md +++ b/core/README.md @@ -280,47 +280,6 @@ export default function Demo() { ## Render -```tsx mdx:preview -import React from 'react'; -import JsonView from '@uiw/react-json-view'; - -const object = { - string: 'Lorem ipsum dolor sit amet', - integer: 42, - float: 114.514, - object: { - 'first-child': true, - 'second-child': false, - 'last-child': null, - }, -} -export default function Demo() { - return ( - , - ellipsis: () => , - objectKey: ({ value, keyName, parentName, ...props}) => { - if (keyName === 'integer' && typeof value === 'number' && value > 40) { - return - } - return - } - }} - /> - ) -} -``` - **Preview Picture** ```tsx mdx:preview @@ -333,7 +292,7 @@ const object = { integer: 42, } -function value({ type, children, value, setValue, keyName, visible, ...props }) { +function value({ type, children, value, setValue, keyName, parentValue, visible, ...props }) { if (type === 'string' && /\.(jpg)$/.test(value)) { return ( @@ -386,7 +345,7 @@ export default function Demo() { import React from 'react'; import JsonView from '@uiw/react-json-view'; -function value({ type, children, visible, keyName, value, setValue, ...props }) { +function value({ type, children, visible, keyName, parentValue, value, setValue, ...props }) { if (value instanceof URL) { return ( @@ -513,7 +472,7 @@ const object = { nestedArray: [ [1, 2], [3, 4], { a: 1} ], } -const ObjectKey = ({ value, keyName, parentName, ...reset }) => { +const ObjectKey = ({ value, keyName, parentName, parentValue, ...reset }) => { if (keyName === 'integer' && typeof value === 'number' && value > 40) { return } @@ -731,12 +690,23 @@ export interface JsonViewProps extends React.DetailedHTMLProps['renderValue']; + value?: (props: RenderValueProps) => JSX.Element; copied?: CopiedProps['render']; countInfo?: (props: CountInfoProps) => JSX.Element; countInfoExtra?: (props: Omit, 'editable'>) => JSX.Element; }; } +interface RenderValueProps extends React.HTMLAttributes { + type: TypeProps['type']; + value?: unknown; + parentValue?: T; + data?: T; + visible?: boolean; + quotes?: JsonViewProps['quotes']; + namespace?: Array; + setValue?: React.Dispatch>; + keyName?: ValueViewProps['keyName']; +} declare const JsonView: React.ForwardRefExoticComponent, "ref"> & React.RefAttributes>; export default JsonView; export interface CountInfoProps { diff --git a/core/src/editor/countInfoExtra.tsx b/core/src/editor/countInfoExtra.tsx index d623af58..5817110b 100644 --- a/core/src/editor/countInfoExtra.tsx +++ b/core/src/editor/countInfoExtra.tsx @@ -9,22 +9,40 @@ export interface CountInfoExtraProps extends Partial { value: T; parentValue?: T; keyName?: string | number; - setValue?: React.Dispatch> - setParentValue?: React.Dispatch> + namespace?: Array; + setValue?: React.Dispatch>; + setParentValue?: React.Dispatch>; /** * When a callback function is passed in, add functionality is enabled. The callback is invoked before additions are completed. * @returns {boolean} Returning false from onAdd will prevent the change from being made. */ onAdd?: (keyOrValue: string, newValue: T, value: T, isAdd: boolean) => boolean; /** - * When a callback function is passed in, delete functionality is enabled. The callback is invoked before deletions are completed. - * @returns Returning false from onDelete will prevent the change from being made. + * When a callback function is passed in, delete functionality is enabled. The callback is invoked before deletions are completed. + * @returns Returning false from onDelete will prevent the change from being made. */ - onDelete?: (keyName: string | number, value: T, parentValue: T) => boolean; + onDelete?: ( + keyName: string | number, + value: T, + parentValue: T | null, + opt: { namespace?: Array }, + ) => boolean; } export function CountInfoExtra(props: CountInfoExtraProps) { - const { visible, showTools, editable, keyName, value, parentValue, setValue, setParentValue, onAdd, onDelete } = props; + const { + visible, + showTools, + editable, + keyName, + value, + namespace, + parentValue, + setValue, + setParentValue, + onAdd, + onDelete, + } = props; if (!visible || !showTools) return null; const click = async (event: React.MouseEvent) => { event.stopPropagation(); @@ -38,25 +56,25 @@ export function CountInfoExtra(props: CountInfoExtraProps) setValue!(result as T); } } - } + }; const deleteHandle = async (event: React.MouseEvent) => { event.stopPropagation(); if (onDelete && (keyName || typeof keyName === 'number') && parentValue) { - const maybeDelete = await onDelete(keyName, value, parentValue as T); + const maybeDelete = await onDelete(keyName, value, parentValue as T, { namespace }); if (maybeDelete && setParentValue) { if (Array.isArray(parentValue)) { parentValue.splice(keyName as number, 1); setParentValue([...parentValue] as T); } else if (keyName in parentValue) { delete (parentValue as Record)[keyName as string]; - setParentValue({...parentValue} as T); + setParentValue({ ...parentValue } as T); } } } - } + }; return ( - {editable && onAdd && } + {editable && onAdd && } {editable && onDelete && parentValue && } ); diff --git a/core/src/editor/value.tsx b/core/src/editor/value.tsx index 1fcf1e40..960aa223 100644 --- a/core/src/editor/value.tsx +++ b/core/src/editor/value.tsx @@ -21,6 +21,7 @@ export interface ReValueProps extends React.HTMLAttributes['keyName']; type: TypeProps['type']; value?: unknown; + parentValue?: T; data?: T; visible?: boolean; namespace?: Array; @@ -44,6 +45,7 @@ export function ReValue(props: ReValueProps) { namespace, displayDataTypes, editableValue, + parentValue, onDelete, onEdit, ...reset @@ -116,7 +118,7 @@ export function ReValue(props: ReValueProps) { const result = await onEdit({ type: 'value', value: text, oldValue: curentChild, namespace }); if (result) { setCurentType(typeStr); - setCurentChild(text); + setCurentChild(text as T); } else { const { content: oldChildStr } = getValueString(curentChild); $edit.current.innerHTML = String(oldChildStr); @@ -145,9 +147,12 @@ export function ReValue(props: ReValueProps) { } const deleteHandle = async (evn: React.MouseEvent) => { evn.stopPropagation(); - if (data && keyName && keyName in data && setValue) { - delete (data as Record)[keyName as string]; - setValue({ ...data } as T); + if (data && keyName && keyName in data && setValue && onDelete) { + const maybeDelete = await onDelete(keyName, value as T, parentValue as T, { namespace }); + if (maybeDelete) { + delete (data as Record)[keyName as string]; + setValue({ ...data } as T); + } } }; return ( diff --git a/core/src/index.tsx b/core/src/index.tsx index 8055500c..b884ebad 100644 --- a/core/src/index.tsx +++ b/core/src/index.tsx @@ -2,7 +2,7 @@ import React, { useId } from 'react'; import { forwardRef } from 'react'; import { RooNode } from './node'; import type { SemicolonProps } from './semicolon'; -import type { ValueViewProps } from './value'; +import type { ValueViewProps, TypeProps } from './value'; import type { CopiedProps } from './copied'; import type { EllipsisProps } from './comps/ellipsis'; import type { MetaProps } from './comps/meta'; @@ -17,6 +17,18 @@ export interface CountInfoProps { visible: boolean; } +interface RenderValueProps extends React.HTMLAttributes { + type: TypeProps['type']; + value?: unknown; + parentValue?: T; + data?: T; + visible?: boolean; + quotes?: JsonViewProps['quotes']; + namespace?: Array; + setValue?: React.Dispatch>; + keyName?: ValueViewProps['keyName']; +} + export interface JsonViewProps extends React.DetailedHTMLProps, HTMLDivElement> { /** This property contains your input JSON */ @@ -40,7 +52,7 @@ export interface JsonViewProps /** When set to true, all nodes will be collapsed by default. Use an integer value to collapse at a particular depth. @default false */ collapsed?: boolean | number; /** Callback function for when a treeNode is expanded or collapsed */ - onExpand?: (props: { expand: boolean; value: T; keyid: string; keyName?: string | number; }) => void; + onExpand?: (props: { expand: boolean; value: T; keyid: string; keyName?: string | number }) => void; /** Fires event when you copy */ onCopied?: CopiedProps['onCopied']; /** Redefine interface elements to re-render. */ @@ -49,7 +61,7 @@ export interface JsonViewProps ellipsis?: EllipsisProps['render']; arrow?: JSX.Element; objectKey?: SemicolonProps['render']; - value?: ValueViewProps['renderValue']; + value?: (props: RenderValueProps) => JSX.Element; copied?: CopiedProps['render']; countInfo?: (props: CountInfoProps) => JSX.Element; countInfoExtra?: (props: Omit, 'editable'>) => JSX.Element; diff --git a/core/src/node.tsx b/core/src/node.tsx index 924eb8a1..10036af3 100644 --- a/core/src/node.tsx +++ b/core/src/node.tsx @@ -8,13 +8,21 @@ import { Copied } from './copied'; import { Ellipsis } from './comps/ellipsis'; import { Meta } from './comps/meta'; +function getLength(obj: T) { + try { + return Object.keys(obj).length; + } catch (error) { + return -1; + } +} + export const CountInfo: FC> = ({ children }) => ( ); @@ -80,12 +88,12 @@ export function RooNode(props: RooNodeProps) { displayDataTypes, displayObjectSize, enableClipboard, + level: level + 1, + parentValue: value as T, indentWidth, data: valueData, quotes, setValue: setValueData, - renderBraces: components.braces, - renderValue: components.value, } as ValueViewProps; const arrowStyle = { transform: `rotate(${expand ? '0' : '-90'}deg)`, transition: 'all 0.3s' }; const arrowView = components.arrow ? ( @@ -115,10 +123,25 @@ export function RooNode(props: RooNodeProps) { ? entries.sort(([a], [b]) => (typeof a === 'string' && typeof b === 'string' ? a.localeCompare(b) : 0)) : entries.sort(([a], [b]) => (typeof a === 'string' && typeof b === 'string' ? objectSortKeys(a, b) : 0)); } - let countInfo = {nameKeys.length} items; + let countInfo: JSX.Element | undefined | null = {nameKeys.length}; if (components.countInfo) { countInfo = components.countInfo({ count: nameKeys.length, level, visible: expand }) || countInfo; } + if (!displayObjectSize) countInfo = null; + const countInfoExtra = + components.countInfoExtra && + components.countInfoExtra({ + count: nameKeys.length, + level, + showTools, + keyName, + visible: expand, + value: valueData, + namespace: [...namespace], + parentValue, + setParentValue, + setValue: setValueData, + }); return (
@@ -141,19 +164,8 @@ export function RooNode(props: RooNodeProps) { {!expand && } {!expand && } - {displayObjectSize && countInfo} - {components.countInfoExtra && - components.countInfoExtra({ - count: nameKeys.length, - level, - showTools, - keyName, - visible: expand, - value: valueData, - parentValue, - setParentValue, - setValue: setValueData, - })} + {countInfo} + {countInfoExtra} {enableClipboard && ( )} @@ -170,19 +182,6 @@ export function RooNode(props: RooNodeProps) { {entries.length > 0 && [...entries].map(([key, itemVal], idx) => { const item = itemVal as T; - const renderKey = ( - - ); const isEmpty = (Array.isArray(item) && (item as []).length === 0) || (typeof item === 'object' && @@ -219,11 +218,51 @@ export function RooNode(props: RooNodeProps) { if (typeof item === 'function') { return; } + const renderKey = ( + + ); + const length = Array.isArray(item) ? item.length : getLength(item); + countInfo = {length}; + if (components.countInfo) { + countInfo = components.countInfo({ count: length, level, visible: expand }) || countInfo; + } + // if (length > -1) { + // countInfo = ( + // + // {countInfo} + // {components.countInfoExtra && + // components.countInfoExtra({ + // count: length, + // level, + // showTools, + // keyName, + // visible: expand, + // value: valueData, + // namespace: [...namespace], + // parentValue, + // setParentValue, + // setValue: setValueData, + // })} + // + // ) + // } return ( >> ); -interface RenderValueProps extends React.HTMLAttributes { - type: TypeProps['type']; - value?: unknown; - data?: T; - visible?: boolean; - quotes?: JsonViewProps['quotes']; - namespace?: Array; - setValue?: React.Dispatch>; - keyName?: ValueViewProps['keyName']; -} - export interface ValueViewProps extends React.DetailedHTMLProps, HTMLSpanElement> { keyName?: string | number; value?: unknown; + parentValue?: T; data?: T; displayDataTypes: boolean; displayObjectSize: boolean; @@ -84,10 +73,10 @@ export interface ValueViewProps level?: number; namespace?: Array; quotes?: JsonViewProps['quotes']; + components?: JsonViewProps['components']; renderKey?: JSX.Element; - renderBraces?: MetaProps['render']; + countInfo?: JSX.Element; setValue?: React.Dispatch>; - renderValue?: (props: RenderValueProps) => JSX.Element; } export function getValueString(value: T) { @@ -131,16 +120,17 @@ export function getValueString(value: T) { export function ValueView(props: ValueViewProps) { const { value, + parentValue, setValue, + countInfo, data, keyName, indentWidth, namespace, renderKey, + components = {}, quotes, level, - renderValue, - renderBraces, enableClipboard, displayObjectSize, displayDataTypes, @@ -168,7 +158,11 @@ export function ValueView(props: ValueViewProps) { color = typeMap[type]?.color || ''; const [showTools, setShowTools] = useState(false); - const tools = enableClipboard ? : undefined; + const tools = useMemo( + () => enableClipboard && showTools && , + [enableClipboard, showTools, value], + ); + // const tools = enableClipboard ? : undefined; const eventProps: React.HTMLAttributes = { className: 'w-rjv-line', style: { paddingLeft: indentWidth }, @@ -180,14 +174,15 @@ export function ValueView(props: ValueViewProps) { if (content && typeof content === 'string') { const reView = - renderValue && - renderValue({ + components.value && + components.value({ className: 'w-rjv-value', style: { color, ...style }, type, value, setValue, data, + parentValue, quotes, keyName, namespace, @@ -220,12 +215,11 @@ export function ValueView(props: ValueViewProps) { ); } - const length = Array.isArray(value) ? value.length : Object.keys(value as object).length; const empty = ( - - - {displayObjectSize && {length} items} + + + {countInfo} ); return ( diff --git a/tsconfig.json b/tsconfig.json index d20ce81b..b1fca474 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "resolveJsonModule": true, "isolatedModules": true, "declaration": true, - "baseUrl": "./website", + "baseUrl": ".", "jsx": "react-jsx", "noFallthroughCasesInSwitch": true, "noEmit": true diff --git a/www/src/App.tsx b/www/src/App.tsx index 572fc102..b28bbfb5 100644 --- a/www/src/App.tsx +++ b/www/src/App.tsx @@ -4,7 +4,7 @@ import { Example } from './example/default'; import { ExampleEditor } from './example/editor'; const ExampleWrapper = styled.div` - max-width: 530px; + max-width: 630px; margin: 0 auto; padding-bottom: 3rem; `; diff --git a/www/src/example/default.tsx b/www/src/example/default.tsx index 989c10e3..f88d0ce8 100644 --- a/www/src/example/default.tsx +++ b/www/src/example/default.tsx @@ -28,11 +28,7 @@ const example = { date: new Date('Tue Sep 13 2022 14:07:44 GMT-0500 (Central Daylight Time)'), array: [19, 100.86, 'test', NaN, Infinity], emptyArray: [], - nestedArray: [ - [1, 2], - [3, 4], - { a: 1} - ], + nestedArray: [[1, 2], [3, 4], { a: 1 }], object3: {}, object2: { 'first-child': true, @@ -66,20 +62,20 @@ export function Example() { const [highlightUpdates, setHighlightUpdates] = useState(true); const [objectSortKeys, setObjectSortKeys] = useState(false); const [clipboard, setClipboard] = useState(true); - const [quotes, setQuotes] = useState['quotes']>("\""); + const [quotes, setQuotes] = useState['quotes']>('"'); const [collapsed, setCollapsed] = useState['collapsed']>(true); - const [src, setSrc] = useState({ ...example }) - useEffect(() => { - const loop = () => { - setSrc(src => ({ - ...src, - timer: src.timer + 1 - })) - } - const id = setInterval(loop, 1000) - return () => clearInterval(id) - }, []); + const [src, setSrc] = useState({ ...example }); + // useEffect(() => { + // const loop = () => { + // setSrc(src => ({ + // ...src, + // timer: src.timer + 1 + // })) + // } + // const id = setInterval(loop, 1000) + // return () => clearInterval(id) + // }, []); return ( @@ -109,7 +105,7 @@ export function Example() { Collapsed: setDisplayDataTypes(evn.target.checked)} /> + setDisplayDataTypes(evn.target.checked)} + /> diff --git a/www/src/example/editor.tsx b/www/src/example/editor.tsx index df0a0fce..3f8257cf 100644 --- a/www/src/example/editor.tsx +++ b/www/src/example/editor.tsx @@ -28,11 +28,7 @@ const example = { date: new Date('Tue Sep 13 2022 14:07:44 GMT-0500 (Central Daylight Time)'), array: [19, 100.86, 'test', NaN, Infinity], emptyArray: [], - nestedArray: [ - [1, 2], - [3, 4], - { a: 1} - ], + nestedArray: [[1, 2], [3, 4], { a: 1 }], object3: {}, object2: { 'first-child': true, @@ -65,13 +61,13 @@ export function ExampleEditor() { useEffect(() => { const loop = () => { - setSrc(src => ({ + setSrc((src) => ({ ...src, - timer: src.timer + 1 - })) - } - const id = setInterval(loop, 1000) - return () => clearInterval(id) + timer: src.timer + 1, + })); + }; + const id = setInterval(loop, 1000); + return () => clearInterval(id); }, []); return ( @@ -79,28 +75,33 @@ export function ExampleEditor() { { - console.log('onEdit:', opts) + console.log('onEdit:', opts); return true; }} - onDelete={(keyName, value, parentValue) => { - console.log('onDelete:', keyName, value, parentValue) + onDelete={(keyName, value, parentValue, opts) => { + console.group('On Delete'); + console.log('keyName:', keyName); + console.log('value:', value); + console.log('parentValue:', parentValue); + console.log('opts:', opts); + console.groupEnd(); return true; }} onAdd={(keyOrValue, newValue, value, isAdd) => { - console.log('keyOrValue:', keyOrValue) - console.log('newValue:', newValue) - console.log('value:', value) - console.log('isAdd:', isAdd) + console.log('keyOrValue:', keyOrValue); + console.log('newValue:', newValue); + console.log('value:', value); + console.log('isAdd:', isAdd); return isAdd; }} style={{ ...theme, padding: 6, borderRadius: 6 }} components={{ - objectKey: ({ value, keyName, parentName, ...props}) => { + objectKey: ({ value, keyName, parentName, ...props }) => { if (keyName === 'integer' && typeof value === 'number' && value > 40) { - return + return ; } - return - } + return ; + }, }} />