LCOV - code coverage report
Current view: top level - dashql-app/src/ui/json - json_key_values.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 92.6 % 95 88
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 { useJsonViewerState } from './json_view_state.js';
       5              : import { useNestedExpansionState } from './json_nested_state.js';
       6              : import { useToolVisibilityDispatch } from './json_tool_state.js';
       7              : import { JsonLiteral } from './json_literal.js';
       8              : import { JsonKeyName } from './json_key_name.js';
       9              : import { JsonValue } from './json_value.js';
      10              : import { JsonCopyButton } from './json_copy_button.js';
      11              : import { useUniqueKey } from './unique_key.js';
      12              : 
      13              : interface KeyValuesProps<T extends object> {
      14              :     expandKey?: string;
      15              :     level: number;
      16              :     value: T;
      17              :     parentValue?: T;
      18              :     keyName?: (string | number);
      19              :     keyPath: (string | number)[];
      20              : }
      21              : 
      22            1 : export function JsonKeyValues<T extends object>(props: KeyValuesProps<T>) {
      23            1 :     const value = props.value ?? {};
      24            1 :     const { keyName, expandKey = '', level, keyPath: keyPath = [], parentValue } = props;
      25            1 :     const { objectSortKeys, indentWidth, arrayPageSize = 0, collapsed, shouldExpandNodeInitially } = useJsonViewerState();
      26            1 :     const [arrayPage, setArrayPage] = React.useState({ value, itemCount: arrayPageSize });
      27              : 
      28              :     // Is expanded?
      29            1 :     const expands = useNestedExpansionState();
      30            1 :     const defaultExpanded =
      31            1 :         typeof collapsed === 'boolean' ? !collapsed : typeof collapsed === 'number' ? level <= collapsed : true;
      32            1 :     const isExpanded = expands[expandKey] ?? (shouldExpandNodeInitially ? true : defaultExpanded);
      33            1 :     const shouldExpand =
      34            1 :         shouldExpandNodeInitially && shouldExpandNodeInitially(isExpanded, { value, keyPath, level, keyName, parentValue });
      35              : 
      36            1 :     if (expands[expandKey] === undefined && !shouldExpand) {
      37            1 :         return null;
      38              :     }
      39            1 :     if (!isExpanded) {
      40            1 :         return null;
      41              :     }
      42            1 :     const isMyArray = Array.isArray(value);
      43            1 :     let entries: [key: string | number, value: T][] = isMyArray
      44            1 :         ? Object.entries(value).map((m) => [Number(m[0]), m[1]])
      45            1 :         : Object.entries(value as T);
      46            1 :     if (objectSortKeys) {
      47            0 :         entries =
      48            0 :             objectSortKeys === true
      49            0 :                 ? entries.sort(([a], [b]) => (typeof a === 'string' && typeof b === 'string' ? a.localeCompare(b) : 0))
      50            0 :                 : entries.sort(([a, valA], [b, valB]) =>
      51            0 :                     typeof a === 'string' && typeof b === 'string' ? objectSortKeys(a, b, valA, valB) : 0,
      52            0 :                 );
      53              :     }
      54            1 :     const visibleItemCount = arrayPage.value === value ? arrayPage.itemCount : arrayPageSize;
      55            1 :     const hiddenItemCount = isMyArray && arrayPageSize > 0
      56            1 :         ? Math.max(0, entries.length - visibleItemCount)
      57            1 :         : 0;
      58            1 :     if (hiddenItemCount > 0) {
      59            1 :         entries = entries.slice(0, visibleItemCount);
      60              :     }
      61              : 
      62              :     const style = {
      63            1 :         borderLeft: 'var(--w-rjv-border-left-width, 1px) var(--w-rjv-line-style, solid) var(--w-rjv-line-color, #ebebeb)',
      64            1 :         paddingLeft: indentWidth,
      65            1 :         marginLeft: 6,
      66              :     };
      67            1 :     return (
      68            1 :         <div className="w-rjv-wrap" style={style}>
      69            1 :             {entries.map(([key, val], idx) => {
      70            1 :                 return (
      71            1 :                     <KeyValuesItem parentValue={value} keyName={key} keyPath={[...keyPath, key]} value={val} key={idx} level={level} />
      72              :                 );
      73            1 :             })}
      74            1 :             {hiddenItemCount > 0 && (
      75            1 :                 <button
      76            1 :                     type="button"
      77            1 :                     className={styles.array_load_more}
      78            1 :                     data-testid="json-array-load-more"
      79            1 :                     onClick={() => setArrayPage({ value, itemCount: visibleItemCount + arrayPageSize })}
      80            1 :                 >
      81            1 :                     Show {Math.min(arrayPageSize, hiddenItemCount)} more ({hiddenItemCount} remaining)
      82            1 :                 </button>
      83              :             )}
      84            1 :         </div>
      85              :     );
      86            1 : };
      87              : 
      88            1 : export function KeyValuesItem<T extends object>(props: KeyValuesProps<T>) {
      89            1 :     const dispatch = useToolVisibilityDispatch();
      90            1 :     const subkeyid = useUniqueKey();
      91            1 :     const isMyArray = Array.isArray(props.value);
      92            1 :     const isMySet = props.value instanceof Set;
      93            1 :     const isMyMap = props.value instanceof Map;
      94            1 :     const isDate = props.value instanceof Date;
      95            1 :     const isUrl = props.value instanceof URL;
      96            1 :     const isMyObject = props.value && typeof props.value === 'object' && !isMyArray && !isMySet && !isMyMap && !isDate && !isUrl;
      97            1 :     const isNested = isMyObject || isMyArray || isMySet || isMyMap;
      98            1 :     if (isNested) {
      99            1 :         const mappedValue = isMySet
     100            0 :             ? Array.from(props.value as Set<any>)
     101            1 :             : (isMyMap ? Object.fromEntries(props.value as Iterable<[any, any]>) : props.value);
     102            1 :         return (
     103            1 :             <JsonValue
     104            1 :                 keyName={props.keyName}
     105            1 :                 keyPath={props.keyPath}
     106            1 :                 value={mappedValue}
     107            1 :                 parentValue={props.parentValue}
     108            1 :                 initialValue={props.value}
     109            1 :                 level={props.level + 1}
     110            1 :             />
     111              :         );
     112            1 :     }
     113            1 :     return (
     114            1 :         <div
     115            1 :             className={styles.object_entries_line}
     116            1 :             data-testid="json-entry-line"
     117            1 :             onMouseEnter={() => dispatch({ [subkeyid]: true })}
     118            1 :             onMouseLeave={() => dispatch({ [subkeyid]: false })}
     119              :         >
     120            1 :             <JsonKeyName
     121            1 :                 keyName={props.keyName}
     122            1 :                 keyPath={props.keyPath}
     123            1 :             />
     124            1 :             <JsonLiteral value={props.value} />
     125            1 :             <JsonCopyButton
     126            1 :                 keyName={props.keyName}
     127            1 :                 keyPath={props.keyPath}
     128            1 :                 value={props.value as object}
     129            1 :                 parentValue={props.parentValue}
     130            1 :                 expandKey={subkeyid} />
     131            1 :         </div>
     132              :     );
     133            1 : };
        

Generated by: LCOV version 2.0-1