diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx
index bee0bc98..c324881d 100644
--- a/docs/pages/index.mdx
+++ b/docs/pages/index.mdx
@@ -92,7 +92,7 @@ Check the [source code](https://github.com/TexteaInc/json-viewer/blob/main/docs/
| `keyRenderer` | `{when: (props) => boolean}` | - | Customize a key, if `keyRenderer.when` returns `true`. |
| `valueTypes` | `ValueTypes` | - | Customize value types. |
| `onChange` | `(path, oldVal, newVal) => void` | - | Callback when value changed. |
-| `onCopy` | `(path, value) => void` | - | Callback when value copied, you can use it to customize the copy behavior.
\*Note: you will have to write the data to the clipboard by yourself. |
+| `onCopy` | `(path, value, copy) => void` | - | Callback when value copied, you can use it to customize the copy behavior.
\*Note: you can use the third argument `copy` to copy string to clipborad. |
| `onSelect` | `(path, value) => void` | - | Callback when value selected. |
| `enableClipboard` | `boolean` | `true` | Whether enable clipboard feature. |
| `editable` | `boolean` \|
`(path, currentValue) => boolean` | `false` | Whether enable edit feature. You can pass a function to customize the result. |
diff --git a/src/components/DataKeyPair.tsx b/src/components/DataKeyPair.tsx
index 56846256..9a5d96cb 100644
--- a/src/components/DataKeyPair.tsx
+++ b/src/components/DataKeyPair.tsx
@@ -8,7 +8,7 @@ import { useInspect } from '../hooks/useInspect'
import { useJsonViewerStore } from '../stores/JsonViewerStore'
import { useTypeComponents } from '../stores/typeRegistry'
import type { DataItemProps } from '../type'
-import { getValueSize } from '../utils'
+import { copyString, getValueSize } from '../utils'
import {
CheckIcon,
ChevronRightIcon,
@@ -167,7 +167,7 @@ export const DataKeyPair: FC = (props) => {
onClick={event => {
event.preventDefault()
try {
- copy(path, value)
+ copy(path, value, copyString)
} catch (e) {
// in some case, this will throw error
// fixme: `useAlert` hook
diff --git a/src/hooks/useCopyToClipboard.ts b/src/hooks/useCopyToClipboard.ts
index 31e4520b..bec6592d 100644
--- a/src/hooks/useCopyToClipboard.ts
+++ b/src/hooks/useCopyToClipboard.ts
@@ -1,9 +1,8 @@
-import copyToClipboard from 'copy-to-clipboard'
import { useCallback, useRef, useState } from 'react'
import { useJsonViewerStore } from '../stores/JsonViewerStore'
import type { JsonViewerOnCopy } from '../type'
-import { safeStringify } from '../utils'
+import { copyString, safeStringify } from '../utils'
/**
* useClipboard hook accepts one argument options in which copied status timeout duration is defined (defaults to 2000). Hook returns object with properties:
@@ -25,24 +24,11 @@ export function useClipboard ({ timeout = 2000 } = {}) {
}, [timeout])
const onCopy = useJsonViewerStore(store => store.onCopy)
- const copy = useCallback((path, value: unknown) => {
+ const copy = useCallback(async (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)
- }
+ await onCopy(path, value, copyString)
+ handleCopyResult(true)
} catch (error) {
console.error(
`error when copy ${path.length === 0
@@ -52,18 +38,20 @@ export function useClipboard ({ timeout = 2000 } = {}) {
}]`, error)
}
} else {
- const valueToCopy = safeStringify(
- typeof value === 'function' ? value.toString() : value,
- ' '
- )
- 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)
+ try {
+ const valueToCopy = safeStringify(
+ typeof value === 'function' ? value.toString() : value,
+ ' '
+ )
+ await copyString(valueToCopy)
+ handleCopyResult(true)
+ } catch (error) {
+ console.error(
+ `error when copy ${path.length === 0
+ ? 'src'
+ : `src[${path.join(
+ '.')}`
+ }]`, error)
}
}
}, [handleCopyResult, onCopy])
diff --git a/src/index.tsx b/src/index.tsx
index 10760d89..a5b2c958 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -21,9 +21,9 @@ import {
} from './stores/typeRegistry'
import { darkColorspace, lightColorspace } from './theme/base16'
import type { JsonViewerProps } from './type'
-import { applyValue, createDataType, isCycleReference } from './utils'
+import { applyValue, createDataType, isCycleReference, safeStringify } from './utils'
-export { applyValue, createDataType, isCycleReference }
+export { applyValue, createDataType, isCycleReference, safeStringify }
/**
* @internal
diff --git a/src/type.ts b/src/type.ts
index 55e0968c..7137f1c6 100644
--- a/src/type.ts
+++ b/src/type.ts
@@ -15,12 +15,14 @@ export type JsonViewerOnChange = (
newValue: U /*, type: ChangeType */) => void
/**
- * @param path path to the target value
- * @param value
+ * @param path path to the target value
+ * @param value
+ * @param copy the function to copy the value to clipboard
*/
export type JsonViewerOnCopy = (
path: Path,
- value: U
+ value: U,
+ copy: (value: string) => Promise
) => unknown | Promise
/**
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 9080f5a2..c709f216 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,3 +1,4 @@
+import copyToClipboard from 'copy-to-clipboard'
import type { ComponentType } from 'react'
import type { DataItemProps, EditorProps, Path } from '../type'
@@ -187,6 +188,15 @@ export function segmentArray (arr: T[], size: number): T[][] {
return result
}
+/**
+ * A safe version of `JSON.stringify` that handles circular references and BigInts.
+ *
+ * *This function might be changed in the future to support more types. Use it with caution.*
+ *
+ * @param obj A JavaScript value, usually an object or array, to be converted.
+ * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
+ * @returns
+ */
export function safeStringify (obj: any, space?: string | number) {
const seenValues: any[] = []
@@ -235,3 +245,16 @@ export function safeStringify (obj: any, space?: string | number) {
return JSON.stringify(obj, replacer, space)
}
+
+export async function copyString (value: string) {
+ if ('clipboard' in navigator) {
+ try {
+ await navigator.clipboard.writeText(value)
+ } catch {
+ // When navigator.clipboard throws an error, fallback to copy-to-clipboard package
+ }
+ }
+
+ // fallback to copy-to-clipboard when navigator.clipboard is not available
+ copyToClipboard(value)
+}