diff --git a/README.md b/README.md index 1ffa0129..b22c52de 100644 --- a/README.md +++ b/README.md @@ -248,9 +248,11 @@ A similar callback is executed whenever an item is copied to the clipboard (if p type // Either "path" or "value" depending on whether "Cmd/Ctrl" was pressed stringValue // A nicely stringified version of `value` // (i.e. what the clipboard actually receives) + success // true/false -- whether clipboard copy action actually succeeded + errorMessage// Error detail if success === false ``` -Since there is very little user feedback when clicking "Copy", a good idea would be to present some kind of notification in this callback. +Since there is very little user feedback when clicking "Copy", a good idea would be to present some kind of notification in this callback. There are situations (such as an insecure environment) where the browser won't actually permit any clipboard actions. In this case, the `success` property will be `false`, so you can handle it appropriately. ### Custom Buttons diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 55af2290..6fd60a17 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -363,14 +363,23 @@ function App() { } enableClipboard={ allowCopy - ? ({ stringValue, type }) => - toast({ - title: `${type === 'value' ? 'Value' : 'Path'} copied to clipboard:`, - description: truncate(String(stringValue)), - status: 'success', - duration: 5000, - isClosable: true, - }) + ? ({ stringValue, type, success, errorMessage }) => { + success + ? toast({ + title: `${type === 'value' ? 'Value' : 'Path'} copied to clipboard:`, + description: truncate(String(stringValue)), + status: 'success', + duration: 5000, + isClosable: true, + }) + : toast({ + title: 'Problem copying to clipboard', + description: errorMessage, + status: 'error', + duration: 5000, + isClosable: true, + }) + } : false } restrictEdit={restrictEdit} diff --git a/src/ButtonPanels.tsx b/src/ButtonPanels.tsx index 3b6ecb57..01a0ef94 100644 --- a/src/ButtonPanels.tsx +++ b/src/ButtonPanels.tsx @@ -10,6 +10,7 @@ import { type NodeData, type CustomButtonDefinition, type KeyboardControlsFull, + type JsonData, } from './types' import { getModifier } from './helpers' @@ -67,8 +68,10 @@ export const EditButtons: React.FC = ({ const handleCopy = (e: React.MouseEvent) => { e.stopPropagation() let copyType: CopyType = 'value' - let value + let value: JsonData let stringValue = '' + let success: boolean + let errorMessage: string | null = null if (enableClipboard) { const modifier = getModifier(e) if (modifier && keyboardControls.clipboardModifier.includes(modifier)) { @@ -79,10 +82,39 @@ export const EditButtons: React.FC = ({ value = data stringValue = type ? JSON.stringify(data, null, 2) : String(value) } - void navigator.clipboard?.writeText(stringValue) - if (typeof enableClipboard === 'function') { - enableClipboard({ value, stringValue, path, key, type: copyType }) + if (!navigator.clipboard) { + if (typeof enableClipboard === 'function') + enableClipboard({ + success: false, + value, + stringValue, + path, + key, + type: copyType, + errorMessage: "Can't access clipboard API", + }) + return } + navigator.clipboard + ?.writeText(stringValue) + .then(() => (success = true)) + .catch((err) => { + success = false + errorMessage = err.message + }) + .finally(() => { + if (typeof enableClipboard === 'function') { + enableClipboard({ + success, + errorMessage, + value, + stringValue, + path, + key, + type: copyType, + }) + } + }) } } diff --git a/src/types.ts b/src/types.ts index a67d298f..5c905811 100644 --- a/src/types.ts +++ b/src/types.ts @@ -139,6 +139,8 @@ export type SearchFilterInputFunction = ( export type CopyType = 'path' | 'value' export type CopyFunction = (input: { + success: boolean + errorMessage: string | null key: CollectionKey path: CollectionKey[] value: unknown @@ -208,7 +210,7 @@ export interface NodeData { path: CollectionKey[] level: number index: number - value: unknown + value: JsonData size: number | null parentData: object | null fullData: JsonData