Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"@emotion/styled": "^11.10.6",
"@mui/material": "^5.11.12",
"copy-to-clipboard": "^3.3.3",
"zustand": "^4.1.5"
"zustand": "^4.3.6"
},
"lint-staged": {
"!*.{ts,tsx,js,jsx}": "prettier --write --ignore-unknown",
Expand Down
2 changes: 0 additions & 2 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ const external = [
'@mui/material/styles',
'copy-to-clipboard',
'zustand',
'zustand/context',
'zustand/middleware',
'react',
'react/jsx-runtime',
'react-dom',
Expand Down
46 changes: 24 additions & 22 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import {
ThemeProvider
} from '@mui/material'
import type { FC, ReactElement } from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useCallback, useContext, useEffect, useMemo, useRef } from 'react'

import { DataKeyPair } from './components/DataKeyPair'
import { useThemeDetector } from './hooks/useThemeDetector'
import {
createJsonViewerStore,
JsonViewerProvider,
useJsonViewerStore,
useJsonViewerStoreApi
JsonViewerStoreContext,
useJsonViewerStore
} from './stores/JsonViewerStore'
import {
createTypeRegistryStore, predefined,
TypeRegistryProvider, useTypeRegistryStore
createTypeRegistryStore,
predefined,
TypeRegistryStoreContext,
useTypeRegistryStore
} from './stores/typeRegistry'
import { darkColorspace, lightColorspace } from './theme/base16'
import type { JsonViewerProps } from './type'
Expand All @@ -30,21 +31,21 @@ function useSetIfNotUndefinedEffect<Key extends keyof JsonViewerProps> (
key: Key,
value: JsonViewerProps[Key] | undefined
) {
const api = useJsonViewerStoreApi()
const { setState } = useContext(JsonViewerStoreContext)
useEffect(() => {
if (value !== undefined) {
api.setState({
setState({
[key]: value
})
}
}, [key, value, api])
}, [key, value, setState])
}

/**
* @internal
*/
const JsonViewerInner: FC<JsonViewerProps> = (props) => {
const api = useJsonViewerStoreApi()
const { setState } = useContext(JsonViewerStoreContext)
useSetIfNotUndefinedEffect('value', props.value)
useSetIfNotUndefinedEffect('editable', props.editable)
useSetIfNotUndefinedEffect('indentWidth', props.indentWidth)
Expand All @@ -60,19 +61,19 @@ const JsonViewerInner: FC<JsonViewerProps> = (props) => {
useSetIfNotUndefinedEffect('onSelect', props.onSelect)
useEffect(() => {
if (props.theme === 'light') {
api.setState({
setState({
colorspace: lightColorspace
})
} else if (props.theme === 'dark') {
api.setState({
setState({
colorspace: darkColorspace
})
} else if (typeof props.theme === 'object') {
api.setState({
setState({
colorspace: props.theme
})
}
}, [api, props.theme])
}, [setState, props.theme])
const onceRef = useRef(true)
const predefinedTypes = useMemo(() => predefined(), [])
const registerTypes = useTypeRegistryStore(store => store.registerTypes)
Expand Down Expand Up @@ -143,17 +144,18 @@ export const JsonViewer = function JsonViewer<Value> (props: JsonViewerProps<Val
})
}, [themeType])
const mixedProps = { ...props, theme: themeType }

// eslint-disable-next-line react-hooks/exhaustive-deps
const jsonViewerStore = useMemo(() => createJsonViewerStore(props), [])
const typeRegistryStore = useMemo(() => createTypeRegistryStore(), [])

return (
<ThemeProvider theme={theme}>
<TypeRegistryProvider createStore={createTypeRegistryStore}>
<JsonViewerProvider createStore={() => {
// This function only runs once, so we don't need a memo for this.
// Refs: https://github.com/pmndrs/zustand/blob/77d14b17bc33a6f10f072802fac56aa78510710e/src/context.ts#L36-L38
return createJsonViewerStore(props)
}}>
<TypeRegistryStoreContext.Provider value={typeRegistryStore}>
<JsonViewerStoreContext.Provider value={jsonViewerStore}>
<JsonViewerInner {...mixedProps}/>
</JsonViewerProvider>
</TypeRegistryProvider>
</JsonViewerStoreContext.Provider>
</TypeRegistryStoreContext.Provider>
</ThemeProvider>
)
}
Expand Down
139 changes: 66 additions & 73 deletions src/stores/JsonViewerStore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SetStateAction } from 'react'
import create from 'zustand'
import createContext from 'zustand/context'
import { combine } from 'zustand/middleware'
import { createContext, useContext } from 'react'
import type { StoreApi } from 'zustand'
import { create, useStore } from 'zustand'

import type {
JsonViewerOnChange,
Expand Down Expand Up @@ -38,85 +38,78 @@ export type JsonViewerState<T = unknown> = {
onSelect: JsonViewerOnSelect | undefined
keyRenderer: JsonViewerKeyRenderer
displayObjectSize: boolean
}

export type JsonViewerActions = {
getInspectCache: (path: Path, nestedIndex?: number) => boolean
setInspectCache: (
path: Path, action: SetStateAction<boolean>, nestedIndex?: number) => void
setHover: (path: Path | null, nestedIndex?: number) => void
}

export const createJsonViewerStore = <T = unknown> (props: JsonViewerProps<T>) =>
create(
combine<JsonViewerState<T>, JsonViewerActions>(
{
// provided by user
enableClipboard: props.enableClipboard ?? true,
indentWidth: props.indentWidth ?? 3,
groupArraysAfterLength: props.groupArraysAfterLength ?? 100,
collapseStringsAfterLength:
(props.collapseStringsAfterLength === false)
? Number.MAX_VALUE
: props.collapseStringsAfterLength ?? 50,
maxDisplayLength: props.maxDisplayLength ?? 30,
rootName: props.rootName ?? 'root',
onChange: props.onChange ?? (() => {}),
onCopy: props.onCopy ?? undefined,
onSelect: props.onSelect ?? undefined,
keyRenderer: props.keyRenderer ?? DefaultKeyRenderer,
editable: props.editable ?? false,
defaultInspectDepth: props.defaultInspectDepth ?? 5,
objectSortKeys: props.objectSortKeys ?? false,
quotesOnKeys: props.quotesOnKeys ?? true,
displayDataTypes: props.displayDataTypes ?? true,
// internal state
inspectCache: {},
hoverPath: null,
colorspace: lightColorspace,
value: props.value,
displayObjectSize: props.displayObjectSize ?? true
},
(set, get) => ({
getInspectCache: (path, nestedIndex) => {
const target = nestedIndex !== undefined
? path.join('.') +
export const createJsonViewerStore = <T = unknown> (props: JsonViewerProps<T>) => {
return create<JsonViewerState>()((set, get) => ({
// provided by user
enableClipboard: props.enableClipboard ?? true,
indentWidth: props.indentWidth ?? 3,
groupArraysAfterLength: props.groupArraysAfterLength ?? 100,
collapseStringsAfterLength:
(props.collapseStringsAfterLength === false)
? Number.MAX_VALUE
: props.collapseStringsAfterLength ?? 50,
maxDisplayLength: props.maxDisplayLength ?? 30,
rootName: props.rootName ?? 'root',
onChange: props.onChange ?? (() => {}),
onCopy: props.onCopy ?? undefined,
onSelect: props.onSelect ?? undefined,
keyRenderer: props.keyRenderer ?? DefaultKeyRenderer,
editable: props.editable ?? false,
defaultInspectDepth: props.defaultInspectDepth ?? 5,
objectSortKeys: props.objectSortKeys ?? false,
quotesOnKeys: props.quotesOnKeys ?? true,
displayDataTypes: props.displayDataTypes ?? true,
// internal state
inspectCache: {},
hoverPath: null,
colorspace: lightColorspace,
value: props.value,
displayObjectSize: props.displayObjectSize ?? true,

getInspectCache: (path, nestedIndex) => {
const target = nestedIndex !== undefined
? path.join('.') +
`[${nestedIndex}]nt`
: path.join('.')
return get().inspectCache[target]
},
setInspectCache: (path, action, nestedIndex) => {
const target = nestedIndex !== undefined
? path.join('.') +
: path.join('.')
return get().inspectCache[target]
},
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
}
}))
},
setHover: (path, nestedIndex) => {
set({
hoverPath: path
? ({
path,
nestedIndex
})
: null
})
: path.join('.')
set(state => ({
inspectCache: {
...state.inspectCache,
[target]: typeof action === 'function'
? action(
state.inspectCache[target])
: action
}
}))
},
setHover: (path, nestedIndex) => {
set({
hoverPath: path
? ({ path, nestedIndex })
: null
})
)
)
export type JsonViewerStore = ReturnType<typeof createJsonViewerStore>
}
}))
}

export const {
useStore: useJsonViewerStore,
useStoreApi: useJsonViewerStoreApi,
Provider: JsonViewerProvider
} = createContext<JsonViewerStore>()
export const JsonViewerStoreContext = createContext<StoreApi<JsonViewerState>>(undefined)

export const JsonViewerProvider = JsonViewerStoreContext.Provider

export const useJsonViewerStore = <U extends unknown>(selector: (state: JsonViewerState) => U, equalityFn?: (a: U, b: U) => boolean) => {
const store = useContext(JsonViewerStoreContext)
return useStore(store, selector, equalityFn)
}
53 changes: 25 additions & 28 deletions src/stores/typeRegistry.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Box } from '@mui/material'
import type { SetStateAction } from 'react'
import { memo, useMemo, useState } from 'react'
import create from 'zustand'
import createStore from 'zustand/context'
import { combine } from 'zustand/middleware'
import { createContext, memo, useContext, useMemo, useState } from 'react'
import type { StoreApi } from 'zustand'
import { createStore, useStore } from 'zustand'

import { createEasyType } from '../components/DataTypes/createEasyType'
import {
Expand All @@ -21,35 +20,33 @@ import { useJsonViewerStore } from './JsonViewerStore'

type TypeRegistryState = {
registry: DataType<any>[]
}

type TypeRegistryActions = {
registerTypes: (setState: SetStateAction<DataType<any>[]>) => void
}

export const createTypeRegistryStore = () => create(
combine<TypeRegistryState, TypeRegistryActions>(
{
registry: []
},
(set) => ({
registerTypes: (setState) => {
set(state => ({
registry:
typeof setState === 'function'
? setState(state.registry)
: setState
}))
}
})
)
)
export const createTypeRegistryStore = () => {
return createStore<TypeRegistryState>()((set) => ({
registry: [],

registerTypes: (setState) => {
set(state => ({
registry:
typeof setState === 'function'
? setState(state.registry)
: setState
}))
}
}))
}

export const TypeRegistryStoreContext = createContext<StoreApi<TypeRegistryState>>(undefined)

export const {
Provider: TypeRegistryProvider,
useStore: useTypeRegistryStore,
useStoreApi: useTypeRegistryStoreApi
} = createStore<ReturnType<typeof createTypeRegistryStore>>()
export const TypeRegistryProvider = TypeRegistryStoreContext.Provider

export const useTypeRegistryStore = <U extends unknown>(selector: (state: TypeRegistryState) => U, equalityFn?: (a: U, b: U) => boolean) => {
const store = useContext(TypeRegistryStoreContext)
return useStore(store, selector, equalityFn)
}

const objectType: DataType<object> = {
is: (value) => typeof value === 'object',
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1961,7 +1961,7 @@ __metadata:
typescript: ^4.9.5
vite: ^4.1.4
vitest: ^0.29.2
zustand: ^4.1.5
zustand: ^4.3.6
peerDependencies:
react: ^17 || ^18
react-dom: ^17 || ^18
Expand Down Expand Up @@ -10449,9 +10449,9 @@ __metadata:
languageName: node
linkType: hard

"zustand@npm:^4.1.5":
version: 4.1.5
resolution: "zustand@npm:4.1.5"
"zustand@npm:^4.3.6":
version: 4.3.6
resolution: "zustand@npm:4.3.6"
dependencies:
use-sync-external-store: 1.2.0
peerDependencies:
Expand All @@ -10462,7 +10462,7 @@ __metadata:
optional: true
react:
optional: true
checksum: 13190ee8e8a797c5347b525a7c392be62b2addacdd9645dd20d37ea053f96c7c7067c099c6201e98ebb8d54991f2e04e241cc323f9a25b841d44f0ae048e3afc
checksum: 4d3cec03526f04ff3de6dc45b6f038c47f091836af9660fbf5f682cae1628221102882df20e4048dfe699a43f67424e5d6afc1116f3838a80eea5dd4f95ddaed
languageName: node
linkType: hard

Expand Down