LCOV - code coverage report
Current view: top level - dashql-app/src/ui/json - json_copy_button.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 34.8 % 66 23
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : import * as styles from './json_view.module.css';
       3              : 
       4              : import icons from '@ankoh/dashql-svg-symbols';
       5              : 
       6              : import { useJsonViewerState } from './json_view_state.js';
       7              : import { useToolVisibility } from './json_tool_state.js';
       8              : import { bigIntToString } from './json_literal.js';
       9              : import { classNames } from '../../utils/classnames.js';
      10              : 
      11              : export interface SectionElementResult<T extends object, K = string | number> {
      12              :     value?: T;
      13              :     parentValue?: T;
      14              :     keyName?: K;
      15              :     keyPath?: K[];
      16              : }
      17              : 
      18              : export interface JsonCopyButtonProps<T extends object> extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'value'>, SectionElementResult<T> {
      19              :     expandKey: string;
      20              :     beforeCopy?: (
      21              :         copyText: string,
      22              :         keyName?: string | number,
      23              :         value?: T,
      24              :         parentValue?: T,
      25              :         expandKey?: string,
      26              :         keyPath?: (number | string)[],
      27              :     ) => string;
      28              : }
      29              : 
      30            1 : export function JsonCopyButton<T extends object>(props: JsonCopyButtonProps<T>) {
      31            1 :     const { keyName, value, parentValue, expandKey, keyPath, beforeCopy, ...other } = props;
      32            1 :     const { onCopied, enableClipboard, beforeCopy: globalBeforeCopy } = useJsonViewerState();
      33            1 :     const isShowTools = useToolVisibility(expandKey);
      34            1 :     const [copied, setCopied] = React.useState(false);
      35              : 
      36            1 :     if (enableClipboard === false || !isShowTools) return null;
      37              : 
      38            1 :     const click = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
      39            0 :         event.stopPropagation();
      40            0 :         let copyText = '';
      41            0 :         if (typeof value === 'number' && value === Infinity) {
      42            0 :             copyText = 'Infinity';
      43            0 :         } else if (typeof value === 'number' && isNaN(value)) {
      44            0 :             copyText = 'NaN';
      45            0 :         } else if (typeof value === 'bigint') {
      46            0 :             copyText = bigIntToString(value);
      47            0 :         } else if (value instanceof Date) {
      48            0 :             copyText = value.toLocaleString();
      49              :         } else {
      50            0 :             copyText = JSON.stringify(value, (_, v) => (typeof v === 'bigint' ? bigIntToString(v) : v), 2);
      51              :         }
      52              : 
      53              :         // Apply beforeCopy transformation if provided
      54              :         // Priority: component prop > global prop
      55            0 :         const finalBeforeCopy = beforeCopy || globalBeforeCopy;
      56            0 :         if (finalBeforeCopy && typeof finalBeforeCopy === 'function') {
      57            0 :             copyText = finalBeforeCopy(copyText, keyName, value, parentValue, expandKey, keyPath);
      58              :         }
      59              : 
      60            0 :         onCopied && onCopied(copyText, value);
      61            0 :         setCopied(true);
      62              : 
      63            0 :         const _clipboard = navigator.clipboard || {
      64            0 :             writeText(text: string) {
      65            0 :                 return new Promise((reslove, reject) => {
      66            0 :                     const textarea = document.createElement('textarea');
      67            0 :                     textarea.style.position = 'absolute';
      68            0 :                     textarea.style.opacity = '0';
      69            0 :                     textarea.style.left = '-99999999px';
      70            0 :                     textarea.value = text;
      71            0 :                     document.body.appendChild(textarea);
      72            0 :                     textarea.select();
      73            0 :                     if (!document.execCommand('copy')) {
      74            0 :                         reject();
      75              :                     } else {
      76            0 :                         reslove();
      77              :                     }
      78            0 :                     textarea.remove();
      79            0 :                 });
      80            0 :             },
      81            0 :         };
      82              : 
      83            0 :         _clipboard
      84            0 :             .writeText(copyText)
      85            0 :             .then(() => {
      86            0 :                 const timer = setTimeout(() => {
      87            0 :                     setCopied(false);
      88            0 :                     clearTimeout(timer);
      89            0 :                 }, 3000);
      90            0 :             })
      91            0 :             .catch((_error) => { });
      92            0 :     };
      93              : 
      94            1 :     const sym = copied ? "clipboard_copied" : "clipboard_copy";
      95            1 :     return (
      96            1 :         <button
      97            1 :             {...other}
      98            1 :             type="button"
      99            1 :             aria-label={copied ? 'JSON copied' : 'Copy JSON value'}
     100            1 :             data-testid="json-copy-value"
     101            1 :             className={classNames(styles.copy_button, {
     102            1 :                 [styles.copy_button_copied]: copied
     103            1 :             })}
     104            1 :             onClick={click}
     105              :         >
     106            1 :             <svg viewBox="0 0 32 36" aria-hidden="true">
     107            1 :                 <use xlinkHref={`${icons}#${sym}`} />
     108            1 :             </svg>
     109            1 :         </button>
     110              :     );
     111            1 : };
        

Generated by: LCOV version 2.0-1