diff --git a/examples/basic/pages/index.tsx b/examples/basic/pages/index.tsx index 4548c884..8336e0b6 100644 --- a/examples/basic/pages/index.tsx +++ b/examples/basic/pages/index.tsx @@ -35,9 +35,12 @@ const loopArray = [ loopArray[1] = loopArray +const longArray = Array.from({ length: 1000 }).map((_, i) => i) + const example = { loopObject, loopArray, + longArray, string: 'this is a string', integer: 42, array: [19, 19, 810, 'test', NaN], diff --git a/src/components/DataKeyPair.tsx b/src/components/DataKeyPair.tsx index bad294d0..244be6d7 100644 --- a/src/components/DataKeyPair.tsx +++ b/src/components/DataKeyPair.tsx @@ -18,27 +18,29 @@ import { DataBox } from './mui/DataBox' export type DataKeyPairProps = { value: unknown - nested?: boolean + nestedIndex?: number path: (string | number)[] } -const IconBox = styled(props => )` +const IconBox = styled(props => )` cursor: pointer; padding-left: 0.7rem; ` as typeof Box export const DataKeyPair: React.FC = (props) => { - const { value, path } = props + const { value, path, nestedIndex } = props const [tempValue, setTempValue] = useState(value) const depth = path.length const key = path[depth - 1] const hoverPath = useJsonViewerStore(store => store.hoverPath) const isHover = useMemo(() => { - return hoverPath && path.every((value, index) => value === hoverPath[index]) - }, [hoverPath, path]) + return hoverPath && path.every( + (value, index) => value === hoverPath.path[index] && nestedIndex === + hoverPath.nestedIndex) + }, [hoverPath, path, nestedIndex]) const setHover = useJsonViewerStore(store => store.setHover) const root = useJsonViewerStore(store => store.value) - const [inspect, setInspect] = useInspect(path, value) + const [inspect, setInspect] = useInspect(path, value, nestedIndex) const [editing, setEditing] = useState(false) const onChange = useJsonViewerStore(store => store.onChange) const keyColor = useTextColor() @@ -106,18 +108,18 @@ export const DataKeyPair: React.FC = (props) => { { copied ? ( - + ) : ( - + ) } @@ -146,9 +148,9 @@ export const DataKeyPair: React.FC = (props) => { const KeyRenderer = useJsonViewerStore(store => store.keyRenderer) return ( setHover(path), [setHover, path]) - } + onMouseEnter={ + useCallback(() => setHover(path, nestedIndex), [setHover, path, nestedIndex]) + } > = (props) => { { KeyRenderer.when(downstreamProps) ? - : !props.nested && ( - isNumberKey - ? {displayKey} - : <>"{displayKey}" - ) + : nestedIndex === undefined && ( + isNumberKey + ? {displayKey} + : <>"{displayKey}" + ) } { - !props.nested && ( + nestedIndex === undefined && ( : ) } @@ -188,11 +190,11 @@ export const DataKeyPair: React.FC = (props) => { { editing - ? (Editor && ) + ? (Editor && ) : (Component) ? : {`fallback: ${value}`} + className='data-value-fallback'>{`fallback: ${value}`} } {PostComponent && } {(isHover && expandable && !inspect) && actionIcons} diff --git a/src/components/DataTypes/Object.tsx b/src/components/DataTypes/Object.tsx index 9fa7bb2b..df8e9cce 100644 --- a/src/components/DataTypes/Object.tsx +++ b/src/components/DataTypes/Object.tsx @@ -111,7 +111,7 @@ export const ObjectType: React.FC> = (props) => { return value.map((list, index) => { const path = [...props.path] return ( - + ) }) } else { diff --git a/src/hooks/useInspect.ts b/src/hooks/useInspect.ts index 824ee173..639da586 100644 --- a/src/hooks/useInspect.ts +++ b/src/hooks/useInspect.ts @@ -11,7 +11,7 @@ import { } from '../stores/JsonViewerStore' import { useIsCycleReference } from './useIsCycleReference' -export function useInspect (path: (string | number)[], value: any) { +export function useInspect (path: (string | number)[], value: any, nestedIndex?: number) { const depth = path.length const isTrap = useIsCycleReference(path, value) const getInspectCache = useJsonViewerStore(store => store.getInspectCache) @@ -19,25 +19,25 @@ export function useInspect (path: (string | number)[], value: any) { const defaultInspectDepth = useJsonViewerStore( store => store.defaultInspectDepth) useEffect(() => { - const inspect = getInspectCache(path) + const inspect = getInspectCache(path, nestedIndex) if (inspect === undefined) { - // do not inspect when it is a cycle reference, otherwise there will have a loop - const inspect = isTrap - ? false - : depth < defaultInspectDepth - setInspectCache(path, inspect) + if (nestedIndex !== undefined) { + setInspectCache(path, false, nestedIndex) + } else { + // do not inspect when it is a cycle reference, otherwise there will have a loop + const inspect = isTrap + ? false + : depth < defaultInspectDepth + setInspectCache(path, inspect) + } } - }, [ - defaultInspectDepth, - depth, - getInspectCache, - isTrap, - path, - setInspectCache - ]) + }, [defaultInspectDepth, depth, getInspectCache, isTrap, nestedIndex, path, setInspectCache]) const [inspect, set] = useState(() => { - const shouldInspect = getInspectCache(path) + const shouldInspect = getInspectCache(path, nestedIndex) if (shouldInspect === undefined) { + if (nestedIndex !== undefined) { + return false + } return isTrap ? false : depth < defaultInspectDepth @@ -47,9 +47,9 @@ export function useInspect (path: (string | number)[], value: any) { const setInspect = useCallback>>((apply) => { set((oldState) => { const newState = typeof apply === 'boolean' ? apply : apply(oldState) - setInspectCache(path, newState) + setInspectCache(path, newState, nestedIndex) return newState }) - }, [path, setInspectCache]) + }, [nestedIndex, path, setInspectCache]) return [inspect, setInspect] as const } diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index 1e228b86..6769777d 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -3,7 +3,7 @@ import create from 'zustand' import createContext from 'zustand/context' import { combine } from 'zustand/middleware' -import type { JsonViewerOnChange } from '..' +import type { JsonViewerOnChange, Path } from '..' import type { ColorNamespace } from '../theme/base16' import { lightColorNamespace } from '../theme/base16' import type { JsonViewerKeyRenderer } from '../type' @@ -13,7 +13,7 @@ DefaultKeyRenderer.when = () => false export type JsonViewerState = { inspectCache: Record - hoverPath: (string | number)[] | null + hoverPath: { path: Path; nestedIndex?: number } | null groupArraysAfterLength: number defaultInspectDepth: number colorNamespace: ColorNamespace @@ -25,9 +25,10 @@ export type JsonViewerState = { } export type JsonViewerActions = { - getInspectCache: (path: (string | number)[]) => boolean - setInspectCache: (path: (string | number)[], action: SetStateAction) => void - setHover: (path: (string | number)[] | null) => void + getInspectCache: (path: Path, nestedIndex?: number) => boolean + setInspectCache: ( + path: Path, action: SetStateAction, nestedIndex?: number) => void + setHover: (path: Path | null, nestedIndex?: number) => void } export const createJsonViewerStore = () => @@ -46,21 +47,36 @@ export const createJsonViewerStore = () => keyRenderer: DefaultKeyRenderer }, (set, get) => ({ - getInspectCache: (path) => { - return get().inspectCache[path.join('.')] + getInspectCache: (path, nestedIndex) => { + const target = nestedIndex !== undefined + ? path.join('.') + + `[${nestedIndex}]nt` + : path.join('.') + return get().inspectCache[target] }, - setInspectCache: (path, action) => { - const target = path.join('.') + setInspectCache: (path, action, nestedIndex) => { + const target = nestedIndex !== undefined + ? path.join('.') + + `[${nestedIndex}]nt` + : path.join('.') set(state => ({ inspectCache: { ...state.inspectCache, - [target]: typeof action === 'function' ? action(state.inspectCache[target]) : action + [target]: typeof action === 'function' + ? action( + state.inspectCache[target]) + : action } })) }, - setHover: (path) => { + setHover: (path, nestedIndex) => { set({ hoverPath: path + ? ({ + path, + nestedIndex + }) + : null }) } }) diff --git a/src/stores/typeRegistry.tsx b/src/stores/typeRegistry.tsx index 0b11f730..2af0a5db 100644 --- a/src/stores/typeRegistry.tsx +++ b/src/stores/typeRegistry.tsx @@ -21,9 +21,10 @@ export function matchTypeComponents (value: Value): DataType { for (const T of typeRegistry) { if (T.is(value)) { potential = T - } - if (typeof value === 'object' && value === null) { - return T + if (typeof value === 'object') { + // early return for case like `null` + return T + } } } if (potential === undefined) { diff --git a/src/type.ts b/src/type.ts index 67c2afa9..95a3fd57 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,7 +1,7 @@ import type { Dispatch, SetStateAction } from 'react' import type React from 'react' -type Path = (string | number)[] +export type Path = (string | number)[] export interface DataItemProps { inspect: boolean