@dbcdk/react-components 0.0.195 → 0.0.196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -10
- package/dist/client.cjs +486 -614
- package/dist/client.d.ts +176 -102
- package/dist/client.js +102 -102
- package/dist/components/color-scheme-button/ColorSchemeButton.cjs +7 -4
- package/dist/components/color-scheme-button/ColorSchemeButton.d.ts +7 -3
- package/dist/components/color-scheme-button/ColorSchemeButton.js +7 -4
- package/dist/components/forms/text-area/Textarea.cjs +2 -1
- package/dist/components/forms/text-area/Textarea.d.ts +1 -0
- package/dist/components/forms/text-area/Textarea.js +2 -1
- package/dist/components/highlighted-text/HighlightedText.cjs +6 -3
- package/dist/components/highlighted-text/HighlightedText.js +7 -4
- package/dist/components/json-viewer/JsonNode.cjs +27 -67
- package/dist/components/json-viewer/JsonNode.d.ts +10 -0
- package/dist/components/json-viewer/JsonNode.js +28 -68
- package/dist/components/json-viewer/JsonRow.cjs +85 -0
- package/dist/components/json-viewer/JsonRow.d.ts +5 -0
- package/dist/components/json-viewer/JsonRow.js +79 -0
- package/dist/components/json-viewer/JsonViewer.cjs +36 -253
- package/dist/components/json-viewer/JsonViewer.js +37 -250
- package/dist/components/json-viewer/JsonViewerShell.cjs +208 -0
- package/dist/components/json-viewer/JsonViewerShell.d.ts +25 -0
- package/dist/components/json-viewer/JsonViewerShell.js +202 -0
- package/dist/components/json-viewer/TanstackJsonViewer.cjs +127 -0
- package/dist/components/json-viewer/TanstackJsonViewer.d.ts +17 -0
- package/dist/components/json-viewer/TanstackJsonViewer.js +121 -0
- package/dist/components/json-viewer/types.d.ts +27 -0
- package/dist/components/json-viewer/useJsonViewerState.cjs +111 -0
- package/dist/components/json-viewer/useJsonViewerState.d.ts +42 -0
- package/dist/components/json-viewer/useJsonViewerState.js +108 -0
- package/dist/components/json-viewer/utils.cjs +36 -0
- package/dist/components/json-viewer/utils.d.ts +21 -1
- package/dist/components/json-viewer/utils.js +35 -1
- package/dist/components/lightbox/Lightbox.cjs +2 -2
- package/dist/components/lightbox/Lightbox.d.ts +1 -1
- package/dist/components/lightbox/Lightbox.js +1 -1
- package/dist/components/menu/Menu.cjs +3 -23
- package/dist/components/menu/Menu.js +3 -23
- package/dist/components/menu/Menu.module.css +4 -3
- package/dist/components/metric-tile/MetricTile.cjs +3 -2
- package/dist/components/metric-tile/MetricTile.js +2 -1
- package/dist/components/overlay/tooltip/Tooltip.cjs +7 -2
- package/dist/components/overlay/tooltip/Tooltip.d.ts +7 -1
- package/dist/components/overlay/tooltip/Tooltip.js +7 -2
- package/dist/components/overlay/tooltip/useTooltipTrigger.cjs +7 -1
- package/dist/components/overlay/tooltip/useTooltipTrigger.js +7 -1
- package/dist/components/page-layout/PageLayout.module.css +1 -1
- package/dist/components/table/Table.cjs +45 -16
- package/dist/components/table/Table.d.ts +1 -1
- package/dist/components/table/Table.js +47 -18
- package/dist/components/table/Table.module.css +135 -7
- package/dist/components/table/Table.types.d.ts +47 -1
- package/dist/components/table/TanstackTable.cjs +30 -2
- package/dist/components/table/TanstackTable.js +31 -3
- package/dist/components/table/components/TableBody.d.ts +1 -2
- package/dist/components/table/components/TableHeader.cjs +6 -4
- package/dist/components/table/components/TableHeader.d.ts +2 -1
- package/dist/components/table/components/TableHeader.js +6 -4
- package/dist/components/table/components/TableHeaderCell.cjs +5 -4
- package/dist/components/table/components/TableHeaderCell.d.ts +3 -1
- package/dist/components/table/components/TableHeaderCell.js +5 -4
- package/dist/components/table/components/TableRow.cjs +1 -1
- package/dist/components/table/components/TableRow.d.ts +1 -2
- package/dist/components/table/components/TableRow.js +1 -1
- package/dist/components/table/components/TableSkeletonBody.cjs +30 -0
- package/dist/components/table/components/TableSkeletonBody.d.ts +13 -0
- package/dist/components/table/components/TableSkeletonBody.js +24 -0
- package/dist/components/table/components/table-settings/TableHeaderSettings.cjs +28 -0
- package/dist/components/table/components/table-settings/TableHeaderSettings.d.ts +11 -0
- package/dist/components/table/components/table-settings/TableHeaderSettings.js +26 -0
- package/dist/components/table/components/table-settings/TableSettings.cjs +82 -32
- package/dist/components/table/components/table-settings/TableSettings.d.ts +14 -6
- package/dist/components/table/components/table-settings/TableSettings.js +84 -34
- package/dist/components/table/table.utils.cjs +2 -0
- package/dist/components/table/table.utils.d.ts +6 -0
- package/dist/components/table/table.utils.js +2 -1
- package/dist/components/table/tableSettings.utils.cjs +29 -0
- package/dist/components/table/tableSettings.utils.d.ts +9 -0
- package/dist/components/table/tableSettings.utils.js +24 -0
- package/dist/components/table/tanstackTable.utils.cjs +18 -0
- package/dist/components/table/tanstackTable.utils.js +19 -1
- package/dist/components/theme-button/ThemeButton.cjs +9 -4
- package/dist/components/theme-button/ThemeButton.d.ts +4 -2
- package/dist/components/theme-button/ThemeButton.js +9 -4
- package/dist/components/theme-script/ThemeScript.cjs +10 -4
- package/dist/components/theme-script/ThemeScript.d.ts +9 -1
- package/dist/components/theme-script/ThemeScript.js +10 -4
- package/dist/hooks/useColorScheme.cjs +18 -14
- package/dist/hooks/useColorScheme.d.ts +11 -1
- package/dist/hooks/useColorScheme.js +19 -15
- package/dist/hooks/useGetActiveTheme.cjs +4 -3
- package/dist/hooks/useGetActiveTheme.d.ts +7 -1
- package/dist/hooks/useGetActiveTheme.js +4 -3
- package/dist/hooks/useTableData.cjs +11 -7
- package/dist/hooks/useTableData.d.ts +8 -2
- package/dist/hooks/useTableData.js +11 -7
- package/dist/hooks/useTableSettings.cjs +25 -9
- package/dist/hooks/useTableSettings.d.ts +9 -6
- package/dist/hooks/useTableSettings.js +25 -9
- package/dist/hooks/useTheme.d.ts +6 -0
- package/dist/styles/styles.css +7 -3
- package/dist/styles/themes/dbc/colors.css +1 -1
- package/dist/styles.css +1 -476
- package/dist/tanstack-virtual.cjs +12 -0
- package/dist/tanstack-virtual.d.ts +1 -0
- package/dist/tanstack-virtual.js +1 -0
- package/package.json +13 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useVirtualizer, measureElement } from '@tanstack/react-virtual';
|
|
4
|
+
import { useRef } from 'react';
|
|
5
|
+
import { JsonRow } from './JsonRow';
|
|
6
|
+
import styles from './JsonViewer.module.css';
|
|
7
|
+
import { JsonViewerShell } from './JsonViewerShell';
|
|
8
|
+
import { useJsonViewerState } from './useJsonViewerState';
|
|
9
|
+
import { flattenVisibleTree } from './utils';
|
|
10
|
+
|
|
11
|
+
const ESTIMATED_ROW_HEIGHT = 32;
|
|
12
|
+
function JsonViewer({
|
|
13
|
+
value,
|
|
14
|
+
defaultExpandedDepth = 2,
|
|
15
|
+
expandAll = false,
|
|
16
|
+
defaultViewMode = "tree",
|
|
17
|
+
searchDebounceMs = 800,
|
|
18
|
+
helperText,
|
|
19
|
+
searchPlaceholder = "S\xF8g i n\xF8gler, stier og v\xE6rdier",
|
|
20
|
+
emptySearchText = "Ingen matchende noder fundet.",
|
|
21
|
+
className,
|
|
22
|
+
maxHeight,
|
|
23
|
+
appearance = "dark",
|
|
24
|
+
deleted = false,
|
|
25
|
+
storageKey,
|
|
26
|
+
borderless = false
|
|
27
|
+
}) {
|
|
28
|
+
const state = useJsonViewerState({
|
|
29
|
+
value,
|
|
30
|
+
defaultExpandedDepth,
|
|
31
|
+
expandAll,
|
|
32
|
+
defaultViewMode,
|
|
33
|
+
searchDebounceMs,
|
|
34
|
+
appearance,
|
|
35
|
+
storageKey
|
|
36
|
+
});
|
|
37
|
+
const treePaneRef = useRef(null);
|
|
38
|
+
const measuredHeightsRef = useRef(/* @__PURE__ */ new Map());
|
|
39
|
+
const measuredHeightSumRef = useRef(0);
|
|
40
|
+
const rows = flattenVisibleTree(
|
|
41
|
+
state.safeValue,
|
|
42
|
+
state.expandedNodeIds,
|
|
43
|
+
state.matches,
|
|
44
|
+
state.normalizedQuery
|
|
45
|
+
);
|
|
46
|
+
const virtualizer = useVirtualizer({
|
|
47
|
+
count: rows.length,
|
|
48
|
+
getScrollElement: () => treePaneRef.current,
|
|
49
|
+
estimateSize: () => {
|
|
50
|
+
const measuredCount = measuredHeightsRef.current.size;
|
|
51
|
+
return measuredCount > 0 ? measuredHeightSumRef.current / measuredCount : ESTIMATED_ROW_HEIGHT;
|
|
52
|
+
},
|
|
53
|
+
getItemKey: (index) => rows[index].nodeId,
|
|
54
|
+
overscan: 12,
|
|
55
|
+
measureElement: (element, entry, instance) => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
const height = measureElement(element, entry, instance);
|
|
58
|
+
const nodeId = (_a = rows[instance.indexFromElement(element)]) == null ? void 0 : _a.nodeId;
|
|
59
|
+
if (nodeId) {
|
|
60
|
+
const previous = (_b = measuredHeightsRef.current.get(nodeId)) != null ? _b : 0;
|
|
61
|
+
measuredHeightSumRef.current += height - previous;
|
|
62
|
+
measuredHeightsRef.current.set(nodeId, height);
|
|
63
|
+
}
|
|
64
|
+
return height;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return /* @__PURE__ */ jsx(
|
|
68
|
+
JsonViewerShell,
|
|
69
|
+
{
|
|
70
|
+
state,
|
|
71
|
+
helperText,
|
|
72
|
+
searchPlaceholder,
|
|
73
|
+
emptySearchText,
|
|
74
|
+
className,
|
|
75
|
+
maxHeight,
|
|
76
|
+
borderless,
|
|
77
|
+
deleted,
|
|
78
|
+
treePaneRef,
|
|
79
|
+
children: /* @__PURE__ */ jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative", width: "100%" }, children: virtualizer.getVirtualItems().map((virtualRow) => {
|
|
80
|
+
const row = rows[virtualRow.index];
|
|
81
|
+
return /* @__PURE__ */ jsx(
|
|
82
|
+
"div",
|
|
83
|
+
{
|
|
84
|
+
"data-index": virtualRow.index,
|
|
85
|
+
ref: virtualizer.measureElement,
|
|
86
|
+
className: styles.node,
|
|
87
|
+
style: {
|
|
88
|
+
position: "absolute",
|
|
89
|
+
top: 0,
|
|
90
|
+
left: 0,
|
|
91
|
+
width: "100%",
|
|
92
|
+
// Matches the 1px `gap` the non-virtualized `.node`/`.children`
|
|
93
|
+
// rules put between rows - flex gap doesn't apply here since
|
|
94
|
+
// rows are absolutely positioned rather than flex siblings.
|
|
95
|
+
paddingBottom: "1px",
|
|
96
|
+
transform: `translateY(${virtualRow.start}px)`,
|
|
97
|
+
["--json-indent"]: row.depth
|
|
98
|
+
},
|
|
99
|
+
children: /* @__PURE__ */ jsx(
|
|
100
|
+
JsonRow,
|
|
101
|
+
{
|
|
102
|
+
nodeId: row.nodeId,
|
|
103
|
+
keyName: row.keyName,
|
|
104
|
+
value: row.value,
|
|
105
|
+
query: state.normalizedQuery,
|
|
106
|
+
match: row.match,
|
|
107
|
+
isExpanded: row.isExpanded,
|
|
108
|
+
copied: state.copiedId === row.nodeId,
|
|
109
|
+
onToggleNode: state.handleToggleNode,
|
|
110
|
+
onCopy: state.handleCopy
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
},
|
|
114
|
+
row.nodeId
|
|
115
|
+
);
|
|
116
|
+
}) })
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { JsonViewer };
|
|
@@ -49,3 +49,30 @@ export type JsonNodeProps = {
|
|
|
49
49
|
copiedId: string | null;
|
|
50
50
|
onCopy: (nodeId: string, rawValue: string) => void;
|
|
51
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Presentational, non-recursive rendering of a single row (key/value or
|
|
54
|
+
* container header + optional search path hint). Shared by the recursive
|
|
55
|
+
* `JsonNode` tree and any flat/virtualized renderer - everything it needs is
|
|
56
|
+
* passed as already-resolved values (no Set/Map), so it can be wrapped in
|
|
57
|
+
* `React.memo` with a plain shallow comparison.
|
|
58
|
+
*/
|
|
59
|
+
export type JsonRowProps = {
|
|
60
|
+
nodeId: string;
|
|
61
|
+
keyName?: string;
|
|
62
|
+
value: JsonValue;
|
|
63
|
+
query: string;
|
|
64
|
+
match: SearchMatch | undefined;
|
|
65
|
+
isExpanded: boolean;
|
|
66
|
+
copied: boolean;
|
|
67
|
+
onToggleNode: (nodeId: string) => void;
|
|
68
|
+
onCopy: (nodeId: string, rawValue: string) => void;
|
|
69
|
+
};
|
|
70
|
+
/** One visible row produced by `flattenVisibleTree`, in document order. */
|
|
71
|
+
export type FlatJsonRow = {
|
|
72
|
+
nodeId: string;
|
|
73
|
+
keyName?: string;
|
|
74
|
+
value: JsonValue;
|
|
75
|
+
depth: number;
|
|
76
|
+
match: SearchMatch | undefined;
|
|
77
|
+
isExpanded: boolean;
|
|
78
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var usePersistentState = require('../../hooks/usePersistentState');
|
|
6
|
+
var clipboard_utils = require('../../utils/clipboard.utils');
|
|
7
|
+
var useClipboardStatus = require('./useClipboardStatus');
|
|
8
|
+
var utils = require('./utils');
|
|
9
|
+
|
|
10
|
+
const COPY_ALL_ID = "__json-viewer-copy-all__";
|
|
11
|
+
function useJsonViewerState({
|
|
12
|
+
value,
|
|
13
|
+
defaultExpandedDepth = 2,
|
|
14
|
+
expandAll = false,
|
|
15
|
+
defaultViewMode = "tree",
|
|
16
|
+
searchDebounceMs = 800,
|
|
17
|
+
appearance = "dark",
|
|
18
|
+
storageKey
|
|
19
|
+
}) {
|
|
20
|
+
const [queryInput, setQueryInput] = react.useState("");
|
|
21
|
+
const [query, setQuery] = react.useState("");
|
|
22
|
+
const normalizedQuery = utils.normalizeSearch(query);
|
|
23
|
+
const inputId = react.useId();
|
|
24
|
+
const { copiedId, setCopiedId } = useClipboardStatus.useClipboardStatus();
|
|
25
|
+
const { value: currentAppearance, setValue: setCurrentAppearance } = usePersistentState.usePersistentState({
|
|
26
|
+
initialValue: appearance,
|
|
27
|
+
storageKey: storageKey ? `${storageKey}:appearance` : void 0,
|
|
28
|
+
validate: (stored) => stored === "dark" || stored === "light"
|
|
29
|
+
});
|
|
30
|
+
const { value: viewMode, setValue: setViewMode } = usePersistentState.usePersistentState({
|
|
31
|
+
initialValue: defaultViewMode,
|
|
32
|
+
storageKey: storageKey ? `${storageKey}:viewMode` : void 0,
|
|
33
|
+
validate: (stored) => stored === "tree" || stored === "raw"
|
|
34
|
+
});
|
|
35
|
+
const handleToggleAppearance = () => {
|
|
36
|
+
setCurrentAppearance((current) => current === "dark" ? "light" : "dark");
|
|
37
|
+
};
|
|
38
|
+
react.useEffect(() => {
|
|
39
|
+
const timeoutId = window.setTimeout(() => {
|
|
40
|
+
setQuery(queryInput);
|
|
41
|
+
}, searchDebounceMs);
|
|
42
|
+
return () => window.clearTimeout(timeoutId);
|
|
43
|
+
}, [queryInput, searchDebounceMs]);
|
|
44
|
+
const safeValue = react.useMemo(() => {
|
|
45
|
+
if (utils.isJsonValue(value)) return value;
|
|
46
|
+
return {
|
|
47
|
+
unsupported: {
|
|
48
|
+
besked: "Den angivne v\xE6rdi kan ikke vises som JSON",
|
|
49
|
+
modtagetType: typeof value
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}, [value]);
|
|
53
|
+
const matches = react.useMemo(
|
|
54
|
+
() => normalizedQuery ? utils.collectSearchMatches(safeValue, normalizedQuery) : /* @__PURE__ */ new Map(),
|
|
55
|
+
[normalizedQuery, safeValue]
|
|
56
|
+
);
|
|
57
|
+
const allExpandableNodeIds = react.useMemo(() => utils.collectExpandableNodeIds(safeValue), [safeValue]);
|
|
58
|
+
const [expandedNodeIds, setExpandedNodeIds] = react.useState(
|
|
59
|
+
() => new Set(utils.collectInitiallyExpandedNodeIds(safeValue, defaultExpandedDepth, expandAll))
|
|
60
|
+
);
|
|
61
|
+
const hasResults = normalizedQuery ? matches.size > 0 : true;
|
|
62
|
+
const rawJson = react.useMemo(() => JSON.stringify(safeValue, null, 2), [safeValue]);
|
|
63
|
+
const handleCopy = react.useCallback(
|
|
64
|
+
async (nodeId, rawValue) => {
|
|
65
|
+
const success = await clipboard_utils.copyToClipboard(rawValue);
|
|
66
|
+
setCopiedId(success ? nodeId : null);
|
|
67
|
+
},
|
|
68
|
+
[setCopiedId]
|
|
69
|
+
);
|
|
70
|
+
const handleToggleNode = react.useCallback((nodeId) => {
|
|
71
|
+
setExpandedNodeIds((current) => {
|
|
72
|
+
const next = new Set(current);
|
|
73
|
+
if (next.has(nodeId)) next.delete(nodeId);
|
|
74
|
+
else next.add(nodeId);
|
|
75
|
+
return next;
|
|
76
|
+
});
|
|
77
|
+
}, []);
|
|
78
|
+
const handleCopyAll = () => handleCopy(COPY_ALL_ID, rawJson);
|
|
79
|
+
const handleExpandAll = () => {
|
|
80
|
+
setExpandedNodeIds(new Set(allExpandableNodeIds));
|
|
81
|
+
};
|
|
82
|
+
const handleCollapseAll = () => {
|
|
83
|
+
setExpandedNodeIds(/* @__PURE__ */ new Set());
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
queryInput,
|
|
87
|
+
setQueryInput,
|
|
88
|
+
query,
|
|
89
|
+
setQuery,
|
|
90
|
+
normalizedQuery,
|
|
91
|
+
inputId,
|
|
92
|
+
copiedId,
|
|
93
|
+
currentAppearance,
|
|
94
|
+
handleToggleAppearance,
|
|
95
|
+
viewMode,
|
|
96
|
+
setViewMode,
|
|
97
|
+
safeValue,
|
|
98
|
+
matches,
|
|
99
|
+
expandedNodeIds,
|
|
100
|
+
hasResults,
|
|
101
|
+
rawJson,
|
|
102
|
+
handleCopy,
|
|
103
|
+
handleCopyAll,
|
|
104
|
+
handleExpandAll,
|
|
105
|
+
handleCollapseAll,
|
|
106
|
+
handleToggleNode
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
exports.COPY_ALL_ID = COPY_ALL_ID;
|
|
111
|
+
exports.useJsonViewerState = useJsonViewerState;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { JsonViewerAppearance, JsonViewerProps, JsonViewerViewMode } from './types';
|
|
2
|
+
export declare const COPY_ALL_ID = "__json-viewer-copy-all__";
|
|
3
|
+
export type { JsonViewerViewMode };
|
|
4
|
+
export type JsonViewerState = ReturnType<typeof useJsonViewerState>;
|
|
5
|
+
export type UseJsonViewerStateProps = Pick<JsonViewerProps, 'value' | 'defaultExpandedDepth' | 'expandAll' | 'defaultViewMode' | 'searchDebounceMs' | 'appearance' | 'storageKey'>;
|
|
6
|
+
/**
|
|
7
|
+
* All state and derived data shared by every JsonViewer flavor (the default
|
|
8
|
+
* recursive tree, and any virtualized variant) - toolbar/search/appearance
|
|
9
|
+
* state, expand tracking, and the memoized JSON parsing/search-match/raw-text
|
|
10
|
+
* derivations. Kept dependency-free (no @tanstack/react-virtual import) so it
|
|
11
|
+
* can be used from the main bundle without pulling in optional peer deps.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useJsonViewerState({ value, defaultExpandedDepth, expandAll, defaultViewMode, searchDebounceMs, appearance, storageKey, }: UseJsonViewerStateProps): {
|
|
14
|
+
queryInput: string;
|
|
15
|
+
setQueryInput: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
16
|
+
query: string;
|
|
17
|
+
setQuery: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
18
|
+
normalizedQuery: string;
|
|
19
|
+
inputId: string;
|
|
20
|
+
copiedId: string | null;
|
|
21
|
+
currentAppearance: JsonViewerAppearance;
|
|
22
|
+
handleToggleAppearance: () => void;
|
|
23
|
+
viewMode: JsonViewerViewMode;
|
|
24
|
+
setViewMode: import("react").Dispatch<import("react").SetStateAction<JsonViewerViewMode>>;
|
|
25
|
+
safeValue: string | number | boolean | import("./types").JsonValue[] | {
|
|
26
|
+
[key: string]: import("./types").JsonValue;
|
|
27
|
+
} | {
|
|
28
|
+
unsupported: {
|
|
29
|
+
besked: string;
|
|
30
|
+
modtagetType: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
|
|
31
|
+
};
|
|
32
|
+
} | null;
|
|
33
|
+
matches: Map<any, any>;
|
|
34
|
+
expandedNodeIds: Set<string>;
|
|
35
|
+
hasResults: boolean;
|
|
36
|
+
rawJson: string;
|
|
37
|
+
handleCopy: (nodeId: string, rawValue: string) => Promise<void>;
|
|
38
|
+
handleCopyAll: () => Promise<void>;
|
|
39
|
+
handleExpandAll: () => void;
|
|
40
|
+
handleCollapseAll: () => void;
|
|
41
|
+
handleToggleNode: (nodeId: string) => void;
|
|
42
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useId, useEffect, useMemo, useCallback } from 'react';
|
|
3
|
+
import { usePersistentState } from '../../hooks/usePersistentState';
|
|
4
|
+
import { copyToClipboard } from '../../utils/clipboard.utils';
|
|
5
|
+
import { useClipboardStatus } from './useClipboardStatus';
|
|
6
|
+
import { normalizeSearch, isJsonValue, collectSearchMatches, collectExpandableNodeIds, collectInitiallyExpandedNodeIds } from './utils';
|
|
7
|
+
|
|
8
|
+
const COPY_ALL_ID = "__json-viewer-copy-all__";
|
|
9
|
+
function useJsonViewerState({
|
|
10
|
+
value,
|
|
11
|
+
defaultExpandedDepth = 2,
|
|
12
|
+
expandAll = false,
|
|
13
|
+
defaultViewMode = "tree",
|
|
14
|
+
searchDebounceMs = 800,
|
|
15
|
+
appearance = "dark",
|
|
16
|
+
storageKey
|
|
17
|
+
}) {
|
|
18
|
+
const [queryInput, setQueryInput] = useState("");
|
|
19
|
+
const [query, setQuery] = useState("");
|
|
20
|
+
const normalizedQuery = normalizeSearch(query);
|
|
21
|
+
const inputId = useId();
|
|
22
|
+
const { copiedId, setCopiedId } = useClipboardStatus();
|
|
23
|
+
const { value: currentAppearance, setValue: setCurrentAppearance } = usePersistentState({
|
|
24
|
+
initialValue: appearance,
|
|
25
|
+
storageKey: storageKey ? `${storageKey}:appearance` : void 0,
|
|
26
|
+
validate: (stored) => stored === "dark" || stored === "light"
|
|
27
|
+
});
|
|
28
|
+
const { value: viewMode, setValue: setViewMode } = usePersistentState({
|
|
29
|
+
initialValue: defaultViewMode,
|
|
30
|
+
storageKey: storageKey ? `${storageKey}:viewMode` : void 0,
|
|
31
|
+
validate: (stored) => stored === "tree" || stored === "raw"
|
|
32
|
+
});
|
|
33
|
+
const handleToggleAppearance = () => {
|
|
34
|
+
setCurrentAppearance((current) => current === "dark" ? "light" : "dark");
|
|
35
|
+
};
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const timeoutId = window.setTimeout(() => {
|
|
38
|
+
setQuery(queryInput);
|
|
39
|
+
}, searchDebounceMs);
|
|
40
|
+
return () => window.clearTimeout(timeoutId);
|
|
41
|
+
}, [queryInput, searchDebounceMs]);
|
|
42
|
+
const safeValue = useMemo(() => {
|
|
43
|
+
if (isJsonValue(value)) return value;
|
|
44
|
+
return {
|
|
45
|
+
unsupported: {
|
|
46
|
+
besked: "Den angivne v\xE6rdi kan ikke vises som JSON",
|
|
47
|
+
modtagetType: typeof value
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}, [value]);
|
|
51
|
+
const matches = useMemo(
|
|
52
|
+
() => normalizedQuery ? collectSearchMatches(safeValue, normalizedQuery) : /* @__PURE__ */ new Map(),
|
|
53
|
+
[normalizedQuery, safeValue]
|
|
54
|
+
);
|
|
55
|
+
const allExpandableNodeIds = useMemo(() => collectExpandableNodeIds(safeValue), [safeValue]);
|
|
56
|
+
const [expandedNodeIds, setExpandedNodeIds] = useState(
|
|
57
|
+
() => new Set(collectInitiallyExpandedNodeIds(safeValue, defaultExpandedDepth, expandAll))
|
|
58
|
+
);
|
|
59
|
+
const hasResults = normalizedQuery ? matches.size > 0 : true;
|
|
60
|
+
const rawJson = useMemo(() => JSON.stringify(safeValue, null, 2), [safeValue]);
|
|
61
|
+
const handleCopy = useCallback(
|
|
62
|
+
async (nodeId, rawValue) => {
|
|
63
|
+
const success = await copyToClipboard(rawValue);
|
|
64
|
+
setCopiedId(success ? nodeId : null);
|
|
65
|
+
},
|
|
66
|
+
[setCopiedId]
|
|
67
|
+
);
|
|
68
|
+
const handleToggleNode = useCallback((nodeId) => {
|
|
69
|
+
setExpandedNodeIds((current) => {
|
|
70
|
+
const next = new Set(current);
|
|
71
|
+
if (next.has(nodeId)) next.delete(nodeId);
|
|
72
|
+
else next.add(nodeId);
|
|
73
|
+
return next;
|
|
74
|
+
});
|
|
75
|
+
}, []);
|
|
76
|
+
const handleCopyAll = () => handleCopy(COPY_ALL_ID, rawJson);
|
|
77
|
+
const handleExpandAll = () => {
|
|
78
|
+
setExpandedNodeIds(new Set(allExpandableNodeIds));
|
|
79
|
+
};
|
|
80
|
+
const handleCollapseAll = () => {
|
|
81
|
+
setExpandedNodeIds(/* @__PURE__ */ new Set());
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
queryInput,
|
|
85
|
+
setQueryInput,
|
|
86
|
+
query,
|
|
87
|
+
setQuery,
|
|
88
|
+
normalizedQuery,
|
|
89
|
+
inputId,
|
|
90
|
+
copiedId,
|
|
91
|
+
currentAppearance,
|
|
92
|
+
handleToggleAppearance,
|
|
93
|
+
viewMode,
|
|
94
|
+
setViewMode,
|
|
95
|
+
safeValue,
|
|
96
|
+
matches,
|
|
97
|
+
expandedNodeIds,
|
|
98
|
+
hasResults,
|
|
99
|
+
rawJson,
|
|
100
|
+
handleCopy,
|
|
101
|
+
handleCopyAll,
|
|
102
|
+
handleExpandAll,
|
|
103
|
+
handleCollapseAll,
|
|
104
|
+
handleToggleNode
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { COPY_ALL_ID, useJsonViewerState };
|
|
@@ -85,6 +85,13 @@ function collectSearchMatches(value, query, path = [], key) {
|
|
|
85
85
|
visit(value, path, key);
|
|
86
86
|
return matches;
|
|
87
87
|
}
|
|
88
|
+
function resolveNodeVisibility(nodeId, value, query, match, expandedNodeIds) {
|
|
89
|
+
const isContainer = isJsonArray(value) || isJsonObject(value);
|
|
90
|
+
const shouldRender = query ? Boolean(match) : true;
|
|
91
|
+
const forceOpen = query ? Boolean(match == null ? void 0 : match.descendant) : false;
|
|
92
|
+
const isExpanded = isContainer && Boolean(query || forceOpen || expandedNodeIds.has(nodeId));
|
|
93
|
+
return { isContainer, shouldRender, isExpanded };
|
|
94
|
+
}
|
|
88
95
|
function collectExpandableNodeIds(value, path = []) {
|
|
89
96
|
if (!isJsonArray(value) && !isJsonObject(value)) return [];
|
|
90
97
|
const nodeId = getNodeId(path);
|
|
@@ -109,11 +116,39 @@ function collectInitiallyExpandedNodeIds(value, defaultExpandedDepth, expandAll,
|
|
|
109
116
|
);
|
|
110
117
|
return includeNode ? [nodeId, ...nested] : nested;
|
|
111
118
|
}
|
|
119
|
+
function flattenVisibleTree(value, expandedNodeIds, matches, query, path = [], keyName, depth = 0) {
|
|
120
|
+
const nodeId = getNodeId(path);
|
|
121
|
+
const match = matches.get(nodeId);
|
|
122
|
+
const { isContainer, shouldRender, isExpanded } = resolveNodeVisibility(
|
|
123
|
+
nodeId,
|
|
124
|
+
value,
|
|
125
|
+
query,
|
|
126
|
+
match,
|
|
127
|
+
expandedNodeIds
|
|
128
|
+
);
|
|
129
|
+
if (!shouldRender) return [];
|
|
130
|
+
const row = { nodeId, keyName, value, depth, match, isExpanded };
|
|
131
|
+
if (!isContainer || !isExpanded) return [row];
|
|
132
|
+
const childEntries = isJsonArray(value) ? value.map((item, index) => [String(index), item]) : isJsonObject(value) ? Object.entries(value) : [];
|
|
133
|
+
const childRows = childEntries.flatMap(
|
|
134
|
+
([childKey, childValue]) => flattenVisibleTree(
|
|
135
|
+
childValue,
|
|
136
|
+
expandedNodeIds,
|
|
137
|
+
matches,
|
|
138
|
+
query,
|
|
139
|
+
[...path, childKey],
|
|
140
|
+
isJsonArray(value) ? `[${childKey}]` : childKey,
|
|
141
|
+
depth + 1
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
return [row, ...childRows];
|
|
145
|
+
}
|
|
112
146
|
|
|
113
147
|
exports.capitalize = capitalize;
|
|
114
148
|
exports.collectExpandableNodeIds = collectExpandableNodeIds;
|
|
115
149
|
exports.collectInitiallyExpandedNodeIds = collectInitiallyExpandedNodeIds;
|
|
116
150
|
exports.collectSearchMatches = collectSearchMatches;
|
|
151
|
+
exports.flattenVisibleTree = flattenVisibleTree;
|
|
117
152
|
exports.formatPrimitive = formatPrimitive;
|
|
118
153
|
exports.getNodeId = getNodeId;
|
|
119
154
|
exports.getPathLabel = getPathLabel;
|
|
@@ -124,3 +159,4 @@ exports.isJsonArray = isJsonArray;
|
|
|
124
159
|
exports.isJsonObject = isJsonObject;
|
|
125
160
|
exports.isJsonValue = isJsonValue;
|
|
126
161
|
exports.normalizeSearch = normalizeSearch;
|
|
162
|
+
exports.resolveNodeVisibility = resolveNodeVisibility;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JsonValue, JsonPrimitive, PrimitiveKind, SearchMatch } from './types';
|
|
1
|
+
import type { FlatJsonRow, JsonValue, JsonPrimitive, PrimitiveKind, SearchMatch } from './types';
|
|
2
2
|
export declare function isJsonArray(value: JsonValue): value is JsonValue[];
|
|
3
3
|
export declare function isJsonObject(value: JsonValue): value is {
|
|
4
4
|
[key: string]: JsonValue;
|
|
@@ -13,5 +13,25 @@ export declare function getPathLabel(path: string[]): string;
|
|
|
13
13
|
export declare function normalizeSearch(value: string): string;
|
|
14
14
|
export declare function capitalize(value: string): string;
|
|
15
15
|
export declare function collectSearchMatches(value: JsonValue, query: string, path?: string[], key?: string): Map<string, SearchMatch>;
|
|
16
|
+
/**
|
|
17
|
+
* Whether a node is a container, should render at all (given an active
|
|
18
|
+
* search), and is currently expanded. Shared by the recursive `JsonNode` tree
|
|
19
|
+
* and `flattenVisibleTree` (used by virtualized renderers) so both walkers
|
|
20
|
+
* make identical decisions instead of duplicating - and potentially
|
|
21
|
+
* drifting on - this logic.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveNodeVisibility(nodeId: string, value: JsonValue, query: string, match: SearchMatch | undefined, expandedNodeIds: Set<string>): {
|
|
24
|
+
isContainer: boolean;
|
|
25
|
+
shouldRender: boolean;
|
|
26
|
+
isExpanded: boolean;
|
|
27
|
+
};
|
|
16
28
|
export declare function collectExpandableNodeIds(value: JsonValue, path?: string[]): string[];
|
|
17
29
|
export declare function collectInitiallyExpandedNodeIds(value: JsonValue, defaultExpandedDepth: number, expandAll: boolean, depth?: number, path?: string[]): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Walks the tree once and produces exactly the rows a recursive renderer
|
|
32
|
+
* would show, in document order - for a flat/virtualized renderer that needs
|
|
33
|
+
* a linear list up front instead of deciding per-node during a recursive
|
|
34
|
+
* render. Uses the same `resolveNodeVisibility` decisions as `JsonNode`, so
|
|
35
|
+
* the two stay behaviorally identical.
|
|
36
|
+
*/
|
|
37
|
+
export declare function flattenVisibleTree(value: JsonValue, expandedNodeIds: Set<string>, matches: Map<string, SearchMatch>, query: string, path?: string[], keyName?: string, depth?: number): FlatJsonRow[];
|
|
@@ -83,6 +83,13 @@ function collectSearchMatches(value, query, path = [], key) {
|
|
|
83
83
|
visit(value, path, key);
|
|
84
84
|
return matches;
|
|
85
85
|
}
|
|
86
|
+
function resolveNodeVisibility(nodeId, value, query, match, expandedNodeIds) {
|
|
87
|
+
const isContainer = isJsonArray(value) || isJsonObject(value);
|
|
88
|
+
const shouldRender = query ? Boolean(match) : true;
|
|
89
|
+
const forceOpen = query ? Boolean(match == null ? void 0 : match.descendant) : false;
|
|
90
|
+
const isExpanded = isContainer && Boolean(query || forceOpen || expandedNodeIds.has(nodeId));
|
|
91
|
+
return { isContainer, shouldRender, isExpanded };
|
|
92
|
+
}
|
|
86
93
|
function collectExpandableNodeIds(value, path = []) {
|
|
87
94
|
if (!isJsonArray(value) && !isJsonObject(value)) return [];
|
|
88
95
|
const nodeId = getNodeId(path);
|
|
@@ -107,5 +114,32 @@ function collectInitiallyExpandedNodeIds(value, defaultExpandedDepth, expandAll,
|
|
|
107
114
|
);
|
|
108
115
|
return includeNode ? [nodeId, ...nested] : nested;
|
|
109
116
|
}
|
|
117
|
+
function flattenVisibleTree(value, expandedNodeIds, matches, query, path = [], keyName, depth = 0) {
|
|
118
|
+
const nodeId = getNodeId(path);
|
|
119
|
+
const match = matches.get(nodeId);
|
|
120
|
+
const { isContainer, shouldRender, isExpanded } = resolveNodeVisibility(
|
|
121
|
+
nodeId,
|
|
122
|
+
value,
|
|
123
|
+
query,
|
|
124
|
+
match,
|
|
125
|
+
expandedNodeIds
|
|
126
|
+
);
|
|
127
|
+
if (!shouldRender) return [];
|
|
128
|
+
const row = { nodeId, keyName, value, depth, match, isExpanded };
|
|
129
|
+
if (!isContainer || !isExpanded) return [row];
|
|
130
|
+
const childEntries = isJsonArray(value) ? value.map((item, index) => [String(index), item]) : isJsonObject(value) ? Object.entries(value) : [];
|
|
131
|
+
const childRows = childEntries.flatMap(
|
|
132
|
+
([childKey, childValue]) => flattenVisibleTree(
|
|
133
|
+
childValue,
|
|
134
|
+
expandedNodeIds,
|
|
135
|
+
matches,
|
|
136
|
+
query,
|
|
137
|
+
[...path, childKey],
|
|
138
|
+
isJsonArray(value) ? `[${childKey}]` : childKey,
|
|
139
|
+
depth + 1
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
return [row, ...childRows];
|
|
143
|
+
}
|
|
110
144
|
|
|
111
|
-
export { capitalize, collectExpandableNodeIds, collectInitiallyExpandedNodeIds, collectSearchMatches, formatPrimitive, getNodeId, getPathLabel, getPrimitiveRawValue, getSummary, getValueType, isJsonArray, isJsonObject, isJsonValue, normalizeSearch };
|
|
145
|
+
export { capitalize, collectExpandableNodeIds, collectInitiallyExpandedNodeIds, collectSearchMatches, flattenVisibleTree, formatPrimitive, getNodeId, getPathLabel, getPrimitiveRawValue, getSummary, getValueType, isJsonArray, isJsonObject, isJsonValue, normalizeSearch, resolveNodeVisibility };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var lucideReact = require('lucide-react');
|
|
6
6
|
var react = require('react');
|
|
7
|
-
var
|
|
7
|
+
var MetaBar = require('../../components/meta-bar/MetaBar');
|
|
8
8
|
var Popover = require('../../components/popover/Popover');
|
|
9
9
|
var styles = require('./Lightbox.module.css');
|
|
10
10
|
var Button = require('../button/Button');
|
|
@@ -135,7 +135,7 @@ function Lightbox({
|
|
|
135
135
|
) }),
|
|
136
136
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.thumbnailFooter, children: [
|
|
137
137
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.thumbnailMeta, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
138
|
-
|
|
138
|
+
MetaBar.MetaBar,
|
|
139
139
|
{
|
|
140
140
|
items: [
|
|
141
141
|
{ value: item.height != null ? `H:${item.height}px` : "-" },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { Download, X, ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
|
|
4
4
|
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
5
|
-
import { MetaBar } from '../../
|
|
5
|
+
import { MetaBar } from '../../components/meta-bar/MetaBar';
|
|
6
6
|
import { Popover } from '../../components/popover/Popover';
|
|
7
7
|
import styles from './Lightbox.module.css';
|
|
8
8
|
import { Button } from '../button/Button';
|
|
@@ -192,13 +192,7 @@ const MenuCheckItem = React__namespace.forwardRef(
|
|
|
192
192
|
className,
|
|
193
193
|
...liProps
|
|
194
194
|
}, ref) => {
|
|
195
|
-
const
|
|
196
|
-
const interactiveClass = [
|
|
197
|
-
styles__default.default.interactiveChild,
|
|
198
|
-
styles__default.default.item,
|
|
199
|
-
active ? styles__default.default.active : "",
|
|
200
|
-
isSelected ? styles__default.default.selected : ""
|
|
201
|
-
].filter(Boolean).join(" ");
|
|
195
|
+
const interactiveClass = [styles__default.default.interactiveChild, styles__default.default.item, active ? styles__default.default.active : ""].filter(Boolean).join(" ");
|
|
202
196
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
203
197
|
"li",
|
|
204
198
|
{
|
|
@@ -212,7 +206,6 @@ const MenuCheckItem = React__namespace.forwardRef(
|
|
|
212
206
|
ref: interactiveRef,
|
|
213
207
|
role: interactiveProps == null ? void 0 : interactiveProps.role,
|
|
214
208
|
tabIndex: -1,
|
|
215
|
-
"aria-selected": isSelected || void 0,
|
|
216
209
|
"aria-disabled": disabled || void 0,
|
|
217
210
|
className: interactiveClass,
|
|
218
211
|
...interactiveProps,
|
|
@@ -265,12 +258,7 @@ const MenuRadioItem = React__namespace.forwardRef(
|
|
|
265
258
|
...liProps
|
|
266
259
|
}, ref) => {
|
|
267
260
|
var _a, _b;
|
|
268
|
-
const interactiveClass = [
|
|
269
|
-
styles__default.default.interactiveChild,
|
|
270
|
-
styles__default.default.item,
|
|
271
|
-
active ? styles__default.default.active : "",
|
|
272
|
-
checked ? styles__default.default.selected : ""
|
|
273
|
-
].filter(Boolean).join(" ");
|
|
261
|
+
const interactiveClass = [styles__default.default.interactiveChild, styles__default.default.item, active ? styles__default.default.active : ""].filter(Boolean).join(" ");
|
|
274
262
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
275
263
|
"li",
|
|
276
264
|
{
|
|
@@ -285,7 +273,6 @@ const MenuRadioItem = React__namespace.forwardRef(
|
|
|
285
273
|
role: (_a = interactiveProps == null ? void 0 : interactiveProps.role) != null ? _a : "menuitemradio",
|
|
286
274
|
tabIndex: (_b = interactiveProps == null ? void 0 : interactiveProps.tabIndex) != null ? _b : 0,
|
|
287
275
|
"aria-checked": checked || void 0,
|
|
288
|
-
"aria-selected": checked || void 0,
|
|
289
276
|
"aria-disabled": disabled || void 0,
|
|
290
277
|
className: interactiveClass,
|
|
291
278
|
...interactiveProps,
|
|
@@ -343,13 +330,7 @@ const MenuSwitchItem = React__namespace.forwardRef(
|
|
|
343
330
|
className,
|
|
344
331
|
...liProps
|
|
345
332
|
}, ref) => {
|
|
346
|
-
const
|
|
347
|
-
const interactiveClass = [
|
|
348
|
-
styles__default.default.interactiveChild,
|
|
349
|
-
styles__default.default.item,
|
|
350
|
-
active ? styles__default.default.active : "",
|
|
351
|
-
isSelected ? styles__default.default.selected : ""
|
|
352
|
-
].filter(Boolean).join(" ");
|
|
333
|
+
const interactiveClass = [styles__default.default.interactiveChild, styles__default.default.item, active ? styles__default.default.active : ""].filter(Boolean).join(" ");
|
|
353
334
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
354
335
|
"li",
|
|
355
336
|
{
|
|
@@ -363,7 +344,6 @@ const MenuSwitchItem = React__namespace.forwardRef(
|
|
|
363
344
|
ref: interactiveRef,
|
|
364
345
|
role: interactiveProps == null ? void 0 : interactiveProps.role,
|
|
365
346
|
tabIndex: -1,
|
|
366
|
-
"aria-selected": isSelected || void 0,
|
|
367
347
|
"aria-disabled": disabled || void 0,
|
|
368
348
|
className: interactiveClass,
|
|
369
349
|
...interactiveProps,
|