@dbcdk/react-components 0.0.195 → 0.0.197

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.
Files changed (109) hide show
  1. package/README.md +78 -10
  2. package/dist/client.cjs +486 -614
  3. package/dist/client.d.ts +176 -102
  4. package/dist/client.js +102 -102
  5. package/dist/components/color-scheme-button/ColorSchemeButton.cjs +7 -4
  6. package/dist/components/color-scheme-button/ColorSchemeButton.d.ts +7 -3
  7. package/dist/components/color-scheme-button/ColorSchemeButton.js +7 -4
  8. package/dist/components/forms/text-area/Textarea.cjs +2 -1
  9. package/dist/components/forms/text-area/Textarea.d.ts +1 -0
  10. package/dist/components/forms/text-area/Textarea.js +2 -1
  11. package/dist/components/highlighted-text/HighlightedText.cjs +6 -3
  12. package/dist/components/highlighted-text/HighlightedText.js +7 -4
  13. package/dist/components/json-viewer/JsonNode.cjs +27 -67
  14. package/dist/components/json-viewer/JsonNode.d.ts +10 -0
  15. package/dist/components/json-viewer/JsonNode.js +28 -68
  16. package/dist/components/json-viewer/JsonRow.cjs +85 -0
  17. package/dist/components/json-viewer/JsonRow.d.ts +5 -0
  18. package/dist/components/json-viewer/JsonRow.js +79 -0
  19. package/dist/components/json-viewer/JsonViewer.cjs +36 -253
  20. package/dist/components/json-viewer/JsonViewer.js +37 -250
  21. package/dist/components/json-viewer/JsonViewerShell.cjs +208 -0
  22. package/dist/components/json-viewer/JsonViewerShell.d.ts +25 -0
  23. package/dist/components/json-viewer/JsonViewerShell.js +202 -0
  24. package/dist/components/json-viewer/TanstackJsonViewer.cjs +127 -0
  25. package/dist/components/json-viewer/TanstackJsonViewer.d.ts +17 -0
  26. package/dist/components/json-viewer/TanstackJsonViewer.js +121 -0
  27. package/dist/components/json-viewer/types.d.ts +27 -0
  28. package/dist/components/json-viewer/useJsonViewerState.cjs +111 -0
  29. package/dist/components/json-viewer/useJsonViewerState.d.ts +42 -0
  30. package/dist/components/json-viewer/useJsonViewerState.js +108 -0
  31. package/dist/components/json-viewer/utils.cjs +36 -0
  32. package/dist/components/json-viewer/utils.d.ts +21 -1
  33. package/dist/components/json-viewer/utils.js +35 -1
  34. package/dist/components/lightbox/Lightbox.cjs +2 -2
  35. package/dist/components/lightbox/Lightbox.d.ts +1 -1
  36. package/dist/components/lightbox/Lightbox.js +1 -1
  37. package/dist/components/menu/Menu.cjs +3 -23
  38. package/dist/components/menu/Menu.js +3 -23
  39. package/dist/components/menu/Menu.module.css +4 -3
  40. package/dist/components/metric-tile/MetricTile.cjs +3 -2
  41. package/dist/components/metric-tile/MetricTile.js +2 -1
  42. package/dist/components/overlay/tooltip/Tooltip.cjs +7 -2
  43. package/dist/components/overlay/tooltip/Tooltip.d.ts +7 -1
  44. package/dist/components/overlay/tooltip/Tooltip.js +7 -2
  45. package/dist/components/overlay/tooltip/useTooltipTrigger.cjs +7 -1
  46. package/dist/components/overlay/tooltip/useTooltipTrigger.js +7 -1
  47. package/dist/components/page-layout/PageLayout.module.css +1 -1
  48. package/dist/components/table/Table.cjs +45 -16
  49. package/dist/components/table/Table.d.ts +1 -1
  50. package/dist/components/table/Table.js +47 -18
  51. package/dist/components/table/Table.module.css +135 -7
  52. package/dist/components/table/Table.types.d.ts +47 -1
  53. package/dist/components/table/TanstackTable.cjs +30 -2
  54. package/dist/components/table/TanstackTable.js +31 -3
  55. package/dist/components/table/components/TableBody.d.ts +1 -2
  56. package/dist/components/table/components/TableHeader.cjs +6 -4
  57. package/dist/components/table/components/TableHeader.d.ts +2 -1
  58. package/dist/components/table/components/TableHeader.js +6 -4
  59. package/dist/components/table/components/TableHeaderCell.cjs +5 -4
  60. package/dist/components/table/components/TableHeaderCell.d.ts +3 -1
  61. package/dist/components/table/components/TableHeaderCell.js +5 -4
  62. package/dist/components/table/components/TableRow.cjs +1 -1
  63. package/dist/components/table/components/TableRow.d.ts +1 -2
  64. package/dist/components/table/components/TableRow.js +1 -1
  65. package/dist/components/table/components/TableSkeletonBody.cjs +30 -0
  66. package/dist/components/table/components/TableSkeletonBody.d.ts +13 -0
  67. package/dist/components/table/components/TableSkeletonBody.js +24 -0
  68. package/dist/components/table/components/table-settings/TableHeaderSettings.cjs +28 -0
  69. package/dist/components/table/components/table-settings/TableHeaderSettings.d.ts +11 -0
  70. package/dist/components/table/components/table-settings/TableHeaderSettings.js +26 -0
  71. package/dist/components/table/components/table-settings/TableSettings.cjs +82 -32
  72. package/dist/components/table/components/table-settings/TableSettings.d.ts +14 -6
  73. package/dist/components/table/components/table-settings/TableSettings.js +84 -34
  74. package/dist/components/table/table.utils.cjs +2 -0
  75. package/dist/components/table/table.utils.d.ts +6 -0
  76. package/dist/components/table/table.utils.js +2 -1
  77. package/dist/components/table/tableSettings.utils.cjs +29 -0
  78. package/dist/components/table/tableSettings.utils.d.ts +9 -0
  79. package/dist/components/table/tableSettings.utils.js +24 -0
  80. package/dist/components/table/tanstackTable.utils.cjs +18 -0
  81. package/dist/components/table/tanstackTable.utils.js +19 -1
  82. package/dist/components/theme-button/ThemeButton.cjs +9 -4
  83. package/dist/components/theme-button/ThemeButton.d.ts +4 -2
  84. package/dist/components/theme-button/ThemeButton.js +9 -4
  85. package/dist/components/theme-script/ThemeScript.cjs +10 -4
  86. package/dist/components/theme-script/ThemeScript.d.ts +9 -1
  87. package/dist/components/theme-script/ThemeScript.js +10 -4
  88. package/dist/hooks/useColorScheme.cjs +19 -15
  89. package/dist/hooks/useColorScheme.d.ts +12 -1
  90. package/dist/hooks/useColorScheme.js +20 -16
  91. package/dist/hooks/useGetActiveTheme.cjs +4 -3
  92. package/dist/hooks/useGetActiveTheme.d.ts +7 -1
  93. package/dist/hooks/useGetActiveTheme.js +4 -3
  94. package/dist/hooks/useTableData.cjs +11 -7
  95. package/dist/hooks/useTableData.d.ts +8 -2
  96. package/dist/hooks/useTableData.js +11 -7
  97. package/dist/hooks/useTableSettings.cjs +25 -9
  98. package/dist/hooks/useTableSettings.d.ts +9 -6
  99. package/dist/hooks/useTableSettings.js +25 -9
  100. package/dist/hooks/useTheme.cjs +15 -15
  101. package/dist/hooks/useTheme.d.ts +8 -0
  102. package/dist/hooks/useTheme.js +15 -15
  103. package/dist/styles/styles.css +7 -3
  104. package/dist/styles/themes/dbc/colors.css +1 -1
  105. package/dist/styles.css +1 -476
  106. package/dist/tanstack-virtual.cjs +12 -0
  107. package/dist/tanstack-virtual.d.ts +1 -0
  108. package/dist/tanstack-virtual.js +1 -0
  109. 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 client = require('../../client');
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
- client.MetaBar,
138
+ MetaBar.MetaBar,
139
139
  {
140
140
  items: [
141
141
  { value: item.height != null ? `H:${item.height}px` : "-" },
@@ -1,5 +1,5 @@
1
1
  import type { ReactElement, ReactNode } from 'react';
2
- import { type MetaItem } from '../../client';
2
+ import { type MetaItem } from '../../components/meta-bar/MetaBar';
3
3
  export type LightboxItem = {
4
4
  thumbnail: ReactNode;
5
5
  preview?: ReactNode;
@@ -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 '../../client';
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 isSelected = checked;
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 isSelected = checked;
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,