diff --git a/src/components/DataKeyPair.tsx b/src/components/DataKeyPair.tsx index d38b678b..34606415 100644 --- a/src/components/DataKeyPair.tsx +++ b/src/components/DataKeyPair.tsx @@ -113,11 +113,8 @@ export const DataKeyPair: React.FC = (props) => { event.preventDefault() try { copy( - JSON.stringify( - typeof value === 'function' ? value.toString() : value, - null, - ' ' - ) + path, + value ) } catch (e) { // in some case, this will throw error diff --git a/src/hooks/useCopyToClipboard.ts b/src/hooks/useCopyToClipboard.ts index 9bff7cf3..e72f1616 100644 --- a/src/hooks/useCopyToClipboard.ts +++ b/src/hooks/useCopyToClipboard.ts @@ -1,6 +1,9 @@ import copyToClipboard from 'copy-to-clipboard' import { useCallback, useRef, useState } from 'react' +import { useJsonViewerStore } from '../stores/JsonViewerStore' +import type { JsonViewerOnCopy } from '../type' + /** * useClipboard hook accepts one argument options in which copied status timeout duration is defined (defaults to 2000). Hook returns object with properties: * - copy – function to copy value to clipboard @@ -19,19 +22,51 @@ export function useClipboard ({ timeout = 2000 } = {}) { copyTimeout.current = window.setTimeout(() => setCopied(false), timeout) setCopied(value) }, [timeout]) + const onCopy = useJsonViewerStore(store => store.onCopy) - const copy = useCallback((valueToCopy: string) => { - if ('clipboard' in navigator) { - navigator.clipboard - .writeText(valueToCopy) - .then(() => handleCopyResult(true)) - // When navigator.clipboard throws an error, fallback to copy-to-clipboard package - .catch(() => copyToClipboard(valueToCopy)) + const copy = useCallback((path, value: unknown) => { + if (typeof onCopy === 'function') { + try { + const result = onCopy(path, value) + if (result instanceof Promise) { + result.then(() => { + handleCopyResult(true) + }).catch((error) => { + console.error( + `error when copy ${path.length === 0 + ? 'src' + : `src[${path.join( + '.')}` + }]`, error) + }) + } else { + handleCopyResult(true) + } + } catch (error) { + console.error( + `error when copy ${path.length === 0 + ? 'src' + : `src[${path.join( + '.')}` + }]`, error) + } } else { - // fallback to copy-to-clipboard when navigator.clipboard is not available - copyToClipboard(valueToCopy) + const valueToCopy = JSON.stringify( + typeof value === 'function' ? value.toString() : value, + null, + ' ' + ) + if ('clipboard' in navigator) { + navigator.clipboard.writeText(valueToCopy) + .then(() => handleCopyResult(true)) + // When navigator.clipboard throws an error, fallback to copy-to-clipboard package + .catch(() => copyToClipboard(valueToCopy)) + } else { + // fallback to copy-to-clipboard when navigator.clipboard is not available + copyToClipboard(valueToCopy) + } } - }, [handleCopyResult]) + }, [handleCopyResult, onCopy]) const reset = useCallback(() => { setCopied(false) diff --git a/src/index.tsx b/src/index.tsx index af0b581e..84800160 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -56,6 +56,7 @@ const JsonViewerInner: React.FC = (props) => { useSetIfNotUndefinedEffect('rootName', props.rootName) useSetIfNotUndefinedEffect('displayDataTypes', props.displayDataTypes) useSetIfNotUndefinedEffect('displayObjectSize', props.displayObjectSize) + useSetIfNotUndefinedEffect('onCopy', props.onCopy) useEffect(() => { if (props.theme === 'light') { api.setState({ diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index 7f22a009..08167113 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -3,7 +3,12 @@ import create from 'zustand' import createContext from 'zustand/context' import { combine } from 'zustand/middleware' -import type { JsonViewerOnChange, JsonViewerProps, Path } from '..' +import type { + JsonViewerOnChange, + JsonViewerOnCopy, + JsonViewerProps, + Path +} from '..' import type { Colorspace } from '../theme/base16' import { lightColorspace } from '../theme/base16' import type { JsonViewerKeyRenderer } from '../type' @@ -28,6 +33,7 @@ export type JsonViewerState = { rootName: false | string value: T onChange: JsonViewerOnChange + onCopy: JsonViewerOnCopy | undefined keyRenderer: JsonViewerKeyRenderer displayObjectSize: boolean } @@ -54,6 +60,7 @@ export const createJsonViewerStore = (props: JsonViewerProps) = maxDisplayLength: props.maxDisplayLength ?? 30, rootName: props.rootName ?? 'root', onChange: props.onChange ?? (() => {}), + onCopy: props.onCopy ?? undefined, keyRenderer: props.keyRenderer ?? DefaultKeyRenderer, editable: props.editable ?? false, defaultInspectDepth: props.defaultInspectDepth ?? 5, diff --git a/src/type.ts b/src/type.ts index af4d3fe4..5b868e88 100644 --- a/src/type.ts +++ b/src/type.ts @@ -5,10 +5,24 @@ import type { Colorspace } from './theme/base16' export type Path = (string | number)[] +/** + * @param path path to the target value + * @param oldValue + * @param newValue + */ export type JsonViewerOnChange = ( - path: (string | number)[], oldValue: U, + path: Path, oldValue: U, newValue: U /*, type: ChangeType */) => void +/** + * @param path path to the target value + * @param value + */ +export type JsonViewerOnCopy = ( + path: Path, + value: U +) => unknown | Promise + export interface DataItemProps { inspect: boolean setInspect: Dispatch> @@ -33,7 +47,7 @@ export type DataType = { } export interface JsonViewerKeyRenderer extends React.FC { - when(props: DataItemProps): boolean + when (props: DataItemProps): boolean } export type JsonViewerTheme = 'light' | 'dark' | 'auto' | Colorspace @@ -59,13 +73,8 @@ export type JsonViewerProps = { */ keyRenderer?: JsonViewerKeyRenderer valueTypes?: DataType[] - /** - * - * @param path path to the target value - * @param oldValue - * @param newValue - */ - onChange?: (path: Path, oldValue: U, newValue: U) => void + onChange?: JsonViewerOnChange + onCopy?: JsonViewerOnCopy /** * Whether enable clipboard feature. *