@script-it/json-views-react 0.1.0-beta.1

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 (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +301 -0
  3. package/THIRD_PARTY_NOTICES.txt +4917 -0
  4. package/dist/browser-device.d.ts +21 -0
  5. package/dist/browser-device.js +22 -0
  6. package/dist/file-content-edit-state.d.ts +10 -0
  7. package/dist/file-content-edit-state.js +1 -0
  8. package/dist/file-content-view.d.ts +8 -0
  9. package/dist/file-content-view.js +7 -0
  10. package/dist/index.d.ts +11 -0
  11. package/dist/index.js +8 -0
  12. package/dist/json/copy-json.d.ts +15 -0
  13. package/dist/json/copy-json.js +52 -0
  14. package/dist/json/html-preview.d.ts +5 -0
  15. package/dist/json/html-preview.js +12 -0
  16. package/dist/json/html-view/native-control.d.ts +44 -0
  17. package/dist/json/html-view/native-control.js +96 -0
  18. package/dist/json/html-view/settings.d.ts +2 -0
  19. package/dist/json/html-view/settings.js +58 -0
  20. package/dist/json/html-view/template.d.ts +12 -0
  21. package/dist/json/html-view/template.js +55 -0
  22. package/dist/json/html-view.d.ts +9 -0
  23. package/dist/json/html-view.js +163 -0
  24. package/dist/json/json-content.d.ts +61 -0
  25. package/dist/json/json-content.js +433 -0
  26. package/dist/json/json-source-editor.d.ts +36 -0
  27. package/dist/json/json-source-editor.js +203 -0
  28. package/dist/json/json-view.d.ts +3 -0
  29. package/dist/json/json-view.js +322 -0
  30. package/dist/json/kanban-view.d.ts +20 -0
  31. package/dist/json/kanban-view.js +328 -0
  32. package/dist/json/long-text.d.ts +3 -0
  33. package/dist/json/long-text.js +6 -0
  34. package/dist/json/record-view.d.ts +38 -0
  35. package/dist/json/record-view.js +85 -0
  36. package/dist/json/row-mutations.d.ts +7 -0
  37. package/dist/json/row-mutations.js +93 -0
  38. package/dist/json/schema-value.d.ts +57 -0
  39. package/dist/json/schema-value.js +294 -0
  40. package/dist/json/source-default.d.ts +1 -0
  41. package/dist/json/source-default.js +1 -0
  42. package/dist/json/source-edit-capabilities.d.ts +8 -0
  43. package/dist/json/source-edit-capabilities.js +22 -0
  44. package/dist/json/source-literals.d.ts +15 -0
  45. package/dist/json/source-literals.js +24 -0
  46. package/dist/json/source-recovery.d.ts +32 -0
  47. package/dist/json/source-recovery.js +38 -0
  48. package/dist/json/source-selection.d.ts +3 -0
  49. package/dist/json/source-selection.js +16 -0
  50. package/dist/json/table-view.d.ts +20 -0
  51. package/dist/json/table-view.js +300 -0
  52. package/dist/json/use-kanban-card-drag.d.ts +13 -0
  53. package/dist/json/use-kanban-card-drag.js +255 -0
  54. package/dist/json/use-kanban-column-drag.d.ts +14 -0
  55. package/dist/json/use-kanban-column-drag.js +179 -0
  56. package/dist/json/view-diagnostics.d.ts +4 -0
  57. package/dist/json/view-diagnostics.js +26 -0
  58. package/dist/json/view-model.d.ts +27 -0
  59. package/dist/json/view-model.js +180 -0
  60. package/dist/json/view-save-status.d.ts +12 -0
  61. package/dist/json/view-save-status.js +38 -0
  62. package/dist/json/view-settings.d.ts +36 -0
  63. package/dist/json/view-settings.js +661 -0
  64. package/dist/json/view-types.d.ts +47 -0
  65. package/dist/json/view-types.js +4 -0
  66. package/dist/json/viewers/general/general-json-view.d.ts +46 -0
  67. package/dist/json/viewers/general/general-json-view.js +163 -0
  68. package/dist/json/viewers/general/json-flatten.d.ts +33 -0
  69. package/dist/json/viewers/general/json-flatten.js +51 -0
  70. package/dist/json/viewers/general/json-node-view.d.ts +43 -0
  71. package/dist/json/viewers/general/json-node-view.js +215 -0
  72. package/dist/lib/clipboard.d.ts +1 -0
  73. package/dist/lib/clipboard.js +24 -0
  74. package/dist/lib/cn.d.ts +2 -0
  75. package/dist/lib/cn.js +5 -0
  76. package/dist/lib/file-data.d.ts +7 -0
  77. package/dist/lib/file-data.js +5 -0
  78. package/dist/lib/floating-portal-selectors.d.ts +4 -0
  79. package/dist/lib/floating-portal-selectors.js +4 -0
  80. package/dist/lib/time.d.ts +24 -0
  81. package/dist/lib/time.js +60 -0
  82. package/dist/lib/ui-size.d.ts +28 -0
  83. package/dist/lib/ui-size.js +39 -0
  84. package/dist/lib/use-anchored-position.d.ts +5 -0
  85. package/dist/lib/use-anchored-position.js +65 -0
  86. package/dist/lib/use-dismiss.d.ts +31 -0
  87. package/dist/lib/use-dismiss.js +65 -0
  88. package/dist/primitives/boolean-toggle.d.ts +10 -0
  89. package/dist/primitives/boolean-toggle.js +11 -0
  90. package/dist/primitives/button.d.ts +11 -0
  91. package/dist/primitives/button.js +34 -0
  92. package/dist/primitives/checkbox.d.ts +4 -0
  93. package/dist/primitives/checkbox.js +8 -0
  94. package/dist/primitives/choice-control.d.ts +29 -0
  95. package/dist/primitives/choice-control.js +131 -0
  96. package/dist/primitives/dialog.d.ts +29 -0
  97. package/dist/primitives/dialog.js +25 -0
  98. package/dist/primitives/dropdown-menu.d.ts +37 -0
  99. package/dist/primitives/dropdown-menu.js +39 -0
  100. package/dist/primitives/inline-feedback-action.d.ts +13 -0
  101. package/dist/primitives/inline-feedback-action.js +6 -0
  102. package/dist/primitives/menu-select.d.ts +12 -0
  103. package/dist/primitives/menu-select.js +7 -0
  104. package/dist/primitives/multi-select.d.ts +18 -0
  105. package/dist/primitives/multi-select.js +7 -0
  106. package/dist/primitives/number-input.d.ts +10 -0
  107. package/dist/primitives/number-input.js +8 -0
  108. package/dist/primitives/option-pill.d.ts +13 -0
  109. package/dist/primitives/option-pill.js +50 -0
  110. package/dist/primitives/tabs.d.ts +7 -0
  111. package/dist/primitives/tabs.js +12 -0
  112. package/dist/primitives/textarea.d.ts +4 -0
  113. package/dist/primitives/textarea.js +8 -0
  114. package/dist/primitives/tooltip.d.ts +31 -0
  115. package/dist/primitives/tooltip.js +37 -0
  116. package/dist/standalone.css +113 -0
  117. package/dist/structured-data/atomic-value-editor.d.ts +33 -0
  118. package/dist/structured-data/atomic-value-editor.js +195 -0
  119. package/dist/structured-data/atomic-value.d.ts +3 -0
  120. package/dist/structured-data/atomic-value.js +6 -0
  121. package/dist/structured-data/column-controls.d.ts +21 -0
  122. package/dist/structured-data/column-controls.js +180 -0
  123. package/dist/structured-data/copy-json-action.d.ts +10 -0
  124. package/dist/structured-data/copy-json-action.js +87 -0
  125. package/dist/structured-data/delete-row-action.d.ts +10 -0
  126. package/dist/structured-data/delete-row-action.js +37 -0
  127. package/dist/structured-data/edit-base.d.ts +3 -0
  128. package/dist/structured-data/edit-base.js +8 -0
  129. package/dist/structured-data/filter-value.d.ts +2 -0
  130. package/dist/structured-data/filter-value.js +28 -0
  131. package/dist/structured-data/row-selection-icon.d.ts +9 -0
  132. package/dist/structured-data/row-selection-icon.js +6 -0
  133. package/dist/structured-data/structured-cell-fill-context.d.ts +6 -0
  134. package/dist/structured-data/structured-cell-fill-context.js +7 -0
  135. package/dist/structured-data/structured-data-content.d.ts +1 -0
  136. package/dist/structured-data/structured-data-content.js +1 -0
  137. package/dist/structured-data/table-types.d.ts +108 -0
  138. package/dist/structured-data/table-types.js +1 -0
  139. package/dist/structured-data/use-metadata-persistence.d.ts +15 -0
  140. package/dist/structured-data/use-metadata-persistence.js +18 -0
  141. package/dist/structured-data/use-optimistic-text-document.d.ts +22 -0
  142. package/dist/structured-data/use-optimistic-text-document.js +30 -0
  143. package/dist/structured-data/value-cell.d.ts +15 -0
  144. package/dist/structured-data/value-cell.js +81 -0
  145. package/dist/styles.css +2 -0
  146. package/dist/surface.d.ts +14 -0
  147. package/dist/surface.js +73 -0
  148. package/dist/tabular-data-view.d.ts +5 -0
  149. package/dist/tabular-data-view.js +493 -0
  150. package/dist/viewer-state.d.ts +72 -0
  151. package/dist/viewer-state.js +79 -0
  152. package/dist/widget-registry.d.ts +60 -0
  153. package/dist/widget-registry.js +126 -0
  154. package/dist/widgets/boolean-widget.d.ts +2 -0
  155. package/dist/widgets/boolean-widget.js +15 -0
  156. package/dist/widgets/date-picker-widget.d.ts +18 -0
  157. package/dist/widgets/date-picker-widget.js +265 -0
  158. package/dist/widgets/dropdown-widget.d.ts +10 -0
  159. package/dist/widgets/dropdown-widget.js +19 -0
  160. package/dist/widgets/multi-select-widget.d.ts +11 -0
  161. package/dist/widgets/multi-select-widget.js +46 -0
  162. package/dist/widgets/number-widget.d.ts +2 -0
  163. package/dist/widgets/number-widget.js +19 -0
  164. package/dist/widgets/read-only-field.d.ts +36 -0
  165. package/dist/widgets/read-only-field.js +39 -0
  166. package/dist/widgets/text-widget.d.ts +2 -0
  167. package/dist/widgets/text-widget.js +19 -0
  168. package/dist/widgets/textarea-widget.d.ts +2 -0
  169. package/dist/widgets/textarea-widget.js +34 -0
  170. package/dist/widgets/types.d.ts +53 -0
  171. package/dist/widgets/types.js +1 -0
  172. package/package.json +83 -0
@@ -0,0 +1,47 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { UiSize } from '../lib/ui-size.js';
3
+ import { type ValuePath } from '@script-it/json-views-core';
4
+ import { type CompiledJsonViewMetadata } from '@script-it/json-views-core';
5
+ import { type JsonViewSchemaExternalWidgetRenderer } from './schema-value.js';
6
+ import type { JsonValueReplacement } from '@script-it/json-views-core';
7
+ export interface JsonViewJsonEditing {
8
+ canEditViews?: boolean;
9
+ /** Why view creation is visible but unavailable. */
10
+ viewCreationUnavailableReason?: string;
11
+ /** Embedded/session metadata saves immediately; structural conversions wait for an explicit save. */
12
+ viewSaveMode?: 'automatic' | 'explicit';
13
+ /** Whether a replacement can preserve the source representation at this path. */
14
+ canReplace?: (path: ValuePath) => boolean;
15
+ addColumn?: (arrayPath: ValuePath, property: string, schemaPath: string, descriptor: Record<string, unknown>, viewDeclarationIndex: number) => Promise<void>;
16
+ deleteColumn?: (arrayPath: ValuePath, property: string, schemaPath: string) => Promise<void>;
17
+ append: (arrayPath: ValuePath, value: unknown) => Promise<void>;
18
+ /** Marks a newly appended record as an unfinished draft until its first edit. */
19
+ registerDraftRow?: (path: ValuePath) => void;
20
+ isDraftRow?: (path: ValuePath) => boolean;
21
+ clear: (path: ValuePath) => Promise<void>;
22
+ replace: (path: ValuePath, value: unknown) => Promise<void>;
23
+ replaceMany: (replacements: readonly JsonValueReplacement[]) => Promise<void>;
24
+ setOptionColor?: (schemaPath: string, optionValue: string, color: string) => Promise<void>;
25
+ replaceViews: (views: readonly unknown[]) => Promise<void>;
26
+ removeMany: (paths: ValuePath[]) => Promise<void>;
27
+ saving: boolean;
28
+ }
29
+ export interface JsonViewerProps {
30
+ compiled: CompiledJsonViewMetadata;
31
+ defaultKey: string;
32
+ editing?: JsonViewJsonEditing;
33
+ filePath?: string;
34
+ fillHeight?: boolean;
35
+ renderMarkdown?: (content: string) => ReactNode;
36
+ renderExternalWidget?: JsonViewSchemaExternalWidgetRenderer;
37
+ /** Reports the current location and, when narrowed by a filter/search, matching paths in display order. */
38
+ onCurrentPathChange?: (path: ValuePath, sourcePaths?: readonly ValuePath[]) => void;
39
+ sourceContent?: ReactNode;
40
+ sourceVisible?: boolean;
41
+ onSourceVisibleChange?: (visible: boolean) => void;
42
+ sourceControl?: ReactNode;
43
+ uiSize?: UiSize;
44
+ viewsEnabled?: boolean;
45
+ /** CSV is already tabular, irrespective of JSON prediction confidence. */
46
+ tabularRoot?: boolean;
47
+ }
@@ -0,0 +1,4 @@
1
+ import {} from 'react';
2
+ import {} from '@script-it/json-views-core';
3
+ import {} from '@script-it/json-views-core';
4
+ import {} from './schema-value.js';
@@ -0,0 +1,46 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { UiSize } from '../../../lib/ui-size.js';
3
+ import { type ValuePath } from '@script-it/json-views-core';
4
+ import type { JsonViewSchemaDescriptor } from '@script-it/json-views-core';
5
+ import type { JsonViewSchemaExternalWidgetRenderer } from '../../schema-value.js';
6
+ export interface GeneralJsonRecordTarget {
7
+ path: ValuePath;
8
+ titleKey?: string;
9
+ onBack?: () => void;
10
+ breadcrumbLabels?: readonly string[];
11
+ backLabel?: string;
12
+ }
13
+ interface GeneralJsonViewProps {
14
+ root: unknown;
15
+ defaultKey: string;
16
+ breadcrumb?: {
17
+ ancestors: readonly string[];
18
+ backLabel: string;
19
+ onBack?: () => void;
20
+ };
21
+ /** Absolute source path used as this navigator's root. */
22
+ initialPath?: ValuePath;
23
+ filePath?: string;
24
+ editing?: {
25
+ canReplace?: (path: ValuePath) => boolean;
26
+ clear: (path: ValuePath) => Promise<void>;
27
+ replace: (path: ValuePath, value: unknown) => Promise<void>;
28
+ removeMany: (paths: ValuePath[]) => Promise<void>;
29
+ saving: boolean;
30
+ };
31
+ renderMarkdown?: (content: string) => ReactNode;
32
+ uiSize?: UiSize;
33
+ fillHeight?: boolean;
34
+ schemaForPath?: (path: ValuePath) => JsonViewSchemaDescriptor | undefined;
35
+ renderExternalWidget?: JsonViewSchemaExternalWidgetRenderer;
36
+ onCurrentPathChange?: (path: ValuePath, sourcePaths?: readonly ValuePath[]) => void;
37
+ renderRecord?: (target: GeneralJsonRecordTarget) => ReactNode;
38
+ }
39
+ export declare function NavigationBreadcrumb({ labels, onBack, backLabel }: {
40
+ labels: readonly string[];
41
+ onBack?: () => void;
42
+ backLabel?: string;
43
+ }): import("react").JSX.Element | null;
44
+ /** Owns navigation and display state for the general JSON renderer. */
45
+ export declare function GeneralJsonView({ breadcrumb, root, defaultKey, initialPath, filePath, editing, renderMarkdown, uiSize, fillHeight, schemaForPath, renderExternalWidget, onCurrentPathChange, renderRecord, }: GeneralJsonViewProps): import("react").JSX.Element;
46
+ export {};
@@ -0,0 +1,163 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { ArrowLeft } from 'lucide-react';
4
+ import { useViewerState } from '../../../viewer-state.js';
5
+ import { getValueAtPath, isJsonViewTableCandidate, valuePathKey, VALUE_PATH_MISSING, } from '@script-it/json-views-core';
6
+ import { useJsonViewsRegistries } from '../../../widget-registry.js';
7
+ import { GeneralJsonNodeView } from './json-node-view.js';
8
+ function BreadcrumbSegment({ label }) {
9
+ const characters = Array.from(label);
10
+ const clipped = characters.length > 20;
11
+ const visible = clipped ? `${characters.slice(0, 10).join('')}…` : label;
12
+ return _jsx("span", { title: clipped ? label : undefined, "aria-label": clipped ? label : undefined, children: visible });
13
+ }
14
+ export function NavigationBreadcrumb({ labels, onBack, backLabel }) {
15
+ if (!onBack)
16
+ return null;
17
+ return _jsxs("nav", { "aria-label": "Breadcrumb", className: "mb-3 flex items-center gap-2", children: [_jsx("button", { type: "button", className: "rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground", "aria-label": backLabel ?? 'Back', onClick: onBack, title: "Back", children: _jsx(ArrowLeft, { className: "h-4 w-4" }) }), _jsx("span", { "data-id": "jsonView-breadcrumb-path", className: "flex min-w-0 items-center whitespace-nowrap text-xs text-muted-foreground", children: labels.map((label, index) => _jsxs("span", { className: "contents", children: [index > 0 && _jsx("span", { "aria-hidden": true, className: "mx-1", children: ' / ' }), _jsx(BreadcrumbSegment, { label: label })] }, `${index}:${label}`)) })] });
18
+ }
19
+ function rootHistory(defaultKey, initialPath) {
20
+ return [{ key: defaultKey, path: initialPath, kind: 'value' }];
21
+ }
22
+ function restoreHistory(root, defaultKey, initialPath, state, filePath) {
23
+ const saved = filePath ? state.get(filePath) : undefined;
24
+ if (!saved)
25
+ return rootHistory(defaultKey, initialPath);
26
+ const currentIsInitial = saved.currentPath.length === initialPath.length
27
+ && initialPath.every((segment, index) => saved.currentPath[index] === segment);
28
+ const currentKey = currentIsInitial
29
+ ? defaultKey
30
+ : saved.currentPath.length > 0
31
+ ? String(saved.currentPath[saved.currentPath.length - 1])
32
+ : defaultKey;
33
+ const history = [
34
+ ...saved.stack.map(({ key, path, kind = 'value', titleKey }) => ({ key, path, kind, titleKey })),
35
+ { key: currentKey, path: saved.currentPath, kind: saved.currentKind ?? 'value', titleKey: saved.currentTitleKey },
36
+ ];
37
+ const current = history[history.length - 1];
38
+ const remainsInsideView = current && initialPath.every((segment, index) => current.path[index] === segment);
39
+ return current && remainsInsideView && getValueAtPath(root, current.path) !== VALUE_PATH_MISSING
40
+ ? history
41
+ : rootHistory(defaultKey, initialPath);
42
+ }
43
+ /** Owns navigation and display state for the general JSON renderer. */
44
+ export function GeneralJsonView({ breadcrumb, root, defaultKey, initialPath = [], filePath, editing, renderMarkdown, uiSize, fillHeight = false, schemaForPath, renderExternalWidget, onCurrentPathChange, renderRecord, }) {
45
+ const { widgets } = useJsonViewsRegistries();
46
+ const { general, navigation } = useViewerState();
47
+ const savedGeneralState = filePath ? general.get(filePath) : undefined;
48
+ const [history, setHistory] = useState(() => restoreHistory(root, defaultKey, initialPath, navigation, filePath));
49
+ const scrollContainerRef = useRef(null);
50
+ const scrollSaveTimer = useRef(undefined);
51
+ const scrollPositionsRef = useRef({ ...savedGeneralState?.scrollPositions });
52
+ const tableCellPositionsRef = useRef({});
53
+ const [visibleSourcePaths, setVisibleSourcePaths] = useState({ pathKey: '' });
54
+ const current = history[history.length - 1] ?? rootHistory(defaultKey, initialPath)[0];
55
+ const resolvedNode = getValueAtPath(root, current.path);
56
+ const navigationIsValid = resolvedNode !== VALUE_PATH_MISSING;
57
+ const effectiveHistory = navigationIsValid ? history : rootHistory(defaultKey, initialPath);
58
+ const effectiveCurrent = effectiveHistory[effectiveHistory.length - 1];
59
+ const initialNode = getValueAtPath(root, initialPath);
60
+ const currentNode = navigationIsValid
61
+ ? resolvedNode
62
+ : initialNode === VALUE_PATH_MISSING ? root : initialNode;
63
+ const currentPathKey = valuePathKey(effectiveCurrent.path);
64
+ const currentSourcePaths = visibleSourcePaths.pathKey === currentPathKey ? visibleSourcePaths.paths : undefined;
65
+ const currentSourcePathsKey = JSON.stringify(currentSourcePaths);
66
+ useEffect(() => {
67
+ onCurrentPathChange?.(effectiveCurrent.path, currentSourcePaths);
68
+ }, [currentPathKey, currentSourcePathsKey, onCurrentPathChange]);
69
+ const reportVisibleSourcePaths = useCallback((paths) => {
70
+ setVisibleSourcePaths((current) => current.pathKey === currentPathKey && JSON.stringify(current.paths) === JSON.stringify(paths)
71
+ ? current
72
+ : { pathKey: currentPathKey, paths });
73
+ }, [currentPathKey]);
74
+ useEffect(() => {
75
+ if (!filePath)
76
+ return;
77
+ const next = {
78
+ stack: effectiveHistory.slice(0, -1),
79
+ currentPath: effectiveCurrent.path,
80
+ currentKind: effectiveCurrent.kind,
81
+ currentTitleKey: effectiveCurrent.titleKey,
82
+ };
83
+ if (JSON.stringify(navigation.get(filePath)) !== JSON.stringify(next))
84
+ navigation.set(filePath, next);
85
+ }, [effectiveCurrent.path, effectiveHistory, filePath, navigation]);
86
+ useEffect(() => {
87
+ const frameId = window.requestAnimationFrame(() => {
88
+ if (!scrollContainerRef.current)
89
+ return;
90
+ scrollContainerRef.current.scrollTop = scrollPositionsRef.current[currentPathKey] ?? 0;
91
+ });
92
+ return () => window.cancelAnimationFrame(frameId);
93
+ }, [currentPathKey]);
94
+ const flushScrollPosition = useCallback(() => {
95
+ if (!scrollSaveTimer.current || !filePath)
96
+ return;
97
+ clearTimeout(scrollSaveTimer.current);
98
+ scrollSaveTimer.current = undefined;
99
+ general.set(filePath, {
100
+ ...general.get(filePath),
101
+ scrollPositions: { ...scrollPositionsRef.current },
102
+ });
103
+ }, [filePath, general]);
104
+ const captureScrollPosition = () => {
105
+ scrollPositionsRef.current[currentPathKey] = scrollContainerRef.current?.scrollTop ?? 0;
106
+ if (!filePath)
107
+ return;
108
+ if (scrollSaveTimer.current)
109
+ clearTimeout(scrollSaveTimer.current);
110
+ scrollSaveTimer.current = setTimeout(flushScrollPosition, 120);
111
+ };
112
+ useEffect(() => {
113
+ window.addEventListener('pagehide', flushScrollPosition, true);
114
+ return () => {
115
+ window.removeEventListener('pagehide', flushScrollPosition, true);
116
+ flushScrollPosition();
117
+ };
118
+ }, [flushScrollPosition]);
119
+ const navigateTo = (key, relativePath = [key], record) => {
120
+ const nextPath = [...effectiveCurrent.path, ...relativePath];
121
+ if (getValueAtPath(root, nextPath) === VALUE_PATH_MISSING)
122
+ return;
123
+ captureScrollPosition();
124
+ setHistory((currentHistory) => [
125
+ ...(navigationIsValid ? currentHistory : rootHistory(defaultKey, initialPath)),
126
+ { key, path: nextPath, kind: record ? 'record' : 'value', titleKey: record?.titleKey },
127
+ ]);
128
+ };
129
+ const goBack = () => {
130
+ captureScrollPosition();
131
+ setHistory((currentHistory) => currentHistory.length > 1
132
+ ? currentHistory.slice(0, -1)
133
+ : currentHistory);
134
+ };
135
+ const atomicEditing = editing
136
+ ? {
137
+ canReplace: editing.canReplace,
138
+ clear: editing.clear,
139
+ commit: editing.replace,
140
+ removeMany: editing.removeMany,
141
+ saving: editing.saving,
142
+ }
143
+ : undefined;
144
+ const individualObject = currentNode !== null && typeof currentNode === 'object' && !Array.isArray(currentNode)
145
+ && !isJsonViewTableCandidate(currentNode);
146
+ const currentDescriptor = schemaForPath?.(effectiveCurrent.path);
147
+ const registeredContainer = currentDescriptor && (widgets.get(currentDescriptor.type) || widgets.getDisplay(currentDescriptor.type) || renderExternalWidget);
148
+ if (renderRecord && !registeredContainer && (effectiveCurrent.kind === 'record' || individualObject)) {
149
+ return _jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: renderRecord({
150
+ path: effectiveCurrent.path,
151
+ titleKey: effectiveCurrent.titleKey,
152
+ onBack: effectiveHistory.length > 1 ? goBack : breadcrumb?.onBack,
153
+ breadcrumbLabels: [...(breadcrumb?.ancestors ?? []), ...effectiveHistory.map(({ key }) => key)],
154
+ backLabel: effectiveHistory.length > 1 ? 'Back' : breadcrumb?.backLabel,
155
+ }) });
156
+ }
157
+ return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [_jsx(NavigationBreadcrumb, { labels: [...(breadcrumb?.ancestors ?? []), ...effectiveHistory.map(({ key }) => key)], onBack: effectiveHistory.length > 1 ? goBack : breadcrumb?.onBack, backLabel: effectiveHistory.length > 1 ? 'Back' : breadcrumb?.backLabel }), _jsx("div", { ref: scrollContainerRef, className: fillHeight
158
+ ? 'flex min-h-0 flex-1 flex-col overflow-auto overscroll-x-none'
159
+ : 'flex-1 overflow-auto overscroll-x-none', onScroll: captureScrollPosition, children: _jsx(GeneralJsonNodeView, { node: currentNode, navigateTo: navigateTo, filePath: filePath, currentPathKey: currentPathKey, currentJsonPath: effectiveCurrent.path, currentLabel: effectiveCurrent.key, renderAsMarkdown: typeof currentNode === 'string'
160
+ && schemaForPath?.(effectiveCurrent.path)?.type === 'markdown', getTableCellPosition: (path) => tableCellPositionsRef.current[path], setTableCellPosition: (path, cell) => {
161
+ tableCellPositionsRef.current[path] = cell;
162
+ }, renderMarkdown: renderMarkdown, uiSize: uiSize, fillHeight: fillHeight, atomicEditing: atomicEditing, schemaForPath: schemaForPath, renderExternalWidget: renderExternalWidget, onVisibleSourcePathsChange: reportVisibleSourcePaths }) })] }));
163
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pure helpers for deciding how the JSON viewer should present a value.
3
+ *
4
+ * Kept free of React/DOM so the detection logic is unit-testable on its own.
5
+ */
6
+ /**
7
+ * True for `{...}` values. The `[object Object]` tag excludes arrays, null, and
8
+ * boxed built-ins (Date, Map, RegExp, …) that a bare `typeof` check would admit,
9
+ * keeping this safe if reused outside `JSON.parse` output.
10
+ */
11
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
12
+ /**
13
+ * Describes a `{ key: object, ... }` map that can be flattened into a table:
14
+ * one row per top-level key, the nested object's fields spread across columns.
15
+ */
16
+ export interface DictTableInfo {
17
+ /** Synthetic leading column holding each entry's top-level key. */
18
+ keyColumn: string;
19
+ /** All columns in display order: `keyColumn` first, then nested fields. */
20
+ columns: string[];
21
+ /** One record per entry, shaped `{ [keyColumn]: key, ...nestedObject }`. */
22
+ records: Record<string, unknown>[];
23
+ /** The original top-level keys, parallel to `records`, for path building. */
24
+ keys: string[];
25
+ }
26
+ /**
27
+ * Detect a map of similarly-shaped records — `{ key: {…}, key: {…} }` — so it
28
+ * can be rendered as a flat table instead of a key→"{…}" drill-in list.
29
+ *
30
+ * Uses the same conservative eligibility check as inferred tables. The map key
31
+ * is only a navigation column, not evidence that its values form useful rows.
32
+ */
33
+ export declare function getDictOfObjectsInfo(obj: Record<string, unknown>): DictTableInfo | null;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pure helpers for deciding how the JSON viewer should present a value.
3
+ *
4
+ * Kept free of React/DOM so the detection logic is unit-testable on its own.
5
+ */
6
+ import { isJsonViewTableCandidate } from '@script-it/json-views-core';
7
+ /**
8
+ * True for `{...}` values. The `[object Object]` tag excludes arrays, null, and
9
+ * boxed built-ins (Date, Map, RegExp, …) that a bare `typeof` check would admit,
10
+ * keeping this safe if reused outside `JSON.parse` output.
11
+ */
12
+ export function isPlainObject(value) {
13
+ return Object.prototype.toString.call(value) === '[object Object]';
14
+ }
15
+ /**
16
+ * Detect a map of similarly-shaped records — `{ key: {…}, key: {…} }` — so it
17
+ * can be rendered as a flat table instead of a key→"{…}" drill-in list.
18
+ *
19
+ * Uses the same conservative eligibility check as inferred tables. The map key
20
+ * is only a navigation column, not evidence that its values form useful rows.
21
+ */
22
+ export function getDictOfObjectsInfo(obj) {
23
+ const entries = Object.entries(obj);
24
+ if (!isJsonViewTableCandidate(obj))
25
+ return null;
26
+ const seenColumns = new Set();
27
+ const nestedColumns = [];
28
+ for (const [, value] of entries) {
29
+ if (!isPlainObject(value))
30
+ return null;
31
+ const keys = Object.keys(value);
32
+ for (const key of keys) {
33
+ if (!seenColumns.has(key)) {
34
+ seenColumns.add(key);
35
+ nestedColumns.push(key);
36
+ }
37
+ }
38
+ }
39
+ if (nestedColumns.length === 0)
40
+ return null;
41
+ // Reserve a column for the map keys that can't clash with a nested field.
42
+ let keyColumn = 'key';
43
+ while (seenColumns.has(keyColumn))
44
+ keyColumn = `_${keyColumn}`;
45
+ const keys = entries.map(([key]) => key);
46
+ const records = entries.map(([key, value]) => ({
47
+ [keyColumn]: key,
48
+ ...value,
49
+ }));
50
+ return { keyColumn, columns: [keyColumn, ...nestedColumns], records, keys };
51
+ }
@@ -0,0 +1,43 @@
1
+ /** Renders one location in a general JSON document. */
2
+ import { type ReactNode } from 'react';
3
+ import type { UiSize } from '../../../lib/ui-size.js';
4
+ import { type ValuePath } from '@script-it/json-views-core';
5
+ import type { JsonViewSchemaDescriptor } from '@script-it/json-views-core';
6
+ import { type JsonViewSchemaExternalWidgetRenderer } from '../../schema-value.js';
7
+ export interface GeneralJsonAtomicEditing {
8
+ canReplace?: (path: ValuePath) => boolean;
9
+ clear: (path: ValuePath) => Promise<void>;
10
+ commit: (path: ValuePath, value: unknown) => Promise<void>;
11
+ /** Removes several values as one commit (one file write). Positional paths
12
+ * must arrive deepest-index-first so earlier removals don't shift them. */
13
+ removeMany: (paths: ValuePath[]) => Promise<void>;
14
+ saving: boolean;
15
+ }
16
+ export type GeneralJsonNavigateTo = (key: string, relativePath?: ValuePath, record?: {
17
+ titleKey?: string;
18
+ }) => void;
19
+ export interface GeneralJsonNodeViewProps {
20
+ node: unknown;
21
+ navigateTo: GeneralJsonNavigateTo;
22
+ filePath?: string;
23
+ currentPathKey: string;
24
+ currentJsonPath: ValuePath;
25
+ currentLabel: string;
26
+ renderAsMarkdown: boolean;
27
+ getTableCellPosition?: (path: string) => {
28
+ row: number;
29
+ col: string;
30
+ } | undefined;
31
+ setTableCellPosition?: (path: string, cell: {
32
+ row: number;
33
+ col: string;
34
+ }) => void;
35
+ renderMarkdown?: (content: string) => ReactNode;
36
+ uiSize?: UiSize;
37
+ fillHeight?: boolean;
38
+ atomicEditing?: GeneralJsonAtomicEditing;
39
+ schemaForPath?: (path: ValuePath) => JsonViewSchemaDescriptor | undefined;
40
+ renderExternalWidget?: JsonViewSchemaExternalWidgetRenderer;
41
+ onVisibleSourcePathsChange?: (paths?: readonly ValuePath[]) => void;
42
+ }
43
+ export declare function GeneralJsonNodeView(props: GeneralJsonNodeViewProps): import("react").JSX.Element;
@@ -0,0 +1,215 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Renders one location in a general JSON document. */
3
+ import { useCallback, useMemo, useState } from 'react';
4
+ import { JsonSourceNumber, useJsonSourceLiterals } from '../../source-literals.js';
5
+ import { useJsonViewsRegistries } from '../../../widget-registry.js';
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../../../primitives/tooltip.js';
7
+ import { cn } from '../../../lib/cn.js';
8
+ import { ExpandValueButton, TABULAR_DATA_FRAME_CLASS, TabularObjectArrayView, ValueCell, ValueCellContent, } from '../../../tabular-data-view.js';
9
+ import { getDictOfObjectsInfo, isPlainObject } from './json-flatten.js';
10
+ import { AtomicValueEditor, StructuredValueCellFrame, } from '../../../structured-data/atomic-value-editor.js';
11
+ import { isEditableAtomicValue } from '../../../structured-data/atomic-value.js';
12
+ import { inferJsonViewRecordTitleKey, isJsonViewTableCandidate } from '@script-it/json-views-core';
13
+ import { JsonViewSchemaValueDisplay, JsonViewSchemaValueCell, } from '../../schema-value.js';
14
+ export function GeneralJsonNodeView(props) {
15
+ return _jsx(AnnotatedJsonNodeView, { ...props }, props.currentPathKey);
16
+ }
17
+ function AnnotatedJsonNodeView({ currentPathKey, ...props }) {
18
+ const [expanded, setExpanded] = useState(false);
19
+ const { widgets } = useJsonViewsRegistries();
20
+ const descriptor = props.schemaForPath?.(props.currentJsonPath);
21
+ const container = props.node !== null && typeof props.node === 'object';
22
+ const registered = descriptor && (widgets.get(descriptor.type) || widgets.getDisplay(descriptor.type) || props.renderExternalWidget);
23
+ if (container && descriptor && registered && !expanded) {
24
+ const editing = props.atomicEditing?.canReplace?.(props.currentJsonPath) === false ? undefined : props.atomicEditing;
25
+ return _jsx(JsonViewSchemaValueCell, { sourcePath: props.currentJsonPath, descriptor: descriptor, label: descriptor.title ?? props.currentLabel, value: props.node, onOpen: () => setExpanded(true), onCommit: editing ? (value) => editing.commit(props.currentJsonPath, value) : undefined, onClear: editing ? () => editing.clear(props.currentJsonPath) : undefined, renderExternalWidget: props.renderExternalWidget, renderMarkdown: props.renderMarkdown, saving: editing?.saving });
26
+ }
27
+ return _jsxs(_Fragment, { children: [expanded && _jsx("button", { type: "button", className: "mb-2 w-fit rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground", onClick: () => setExpanded(false), children: "Back to annotated value" }), _jsx(NodeView, { ...props, currentPath: currentPathKey })] });
28
+ }
29
+ function GeneralJsonValueCell({ atomicEditing, compact = false, label, navigateTo, path, renderMarkdown, renderExternalWidget, schemaForPath, value, }) {
30
+ if (path && atomicEditing?.canReplace?.(path) === false)
31
+ atomicEditing = undefined;
32
+ const descriptor = path ? schemaForPath?.(path) : undefined;
33
+ if (descriptor && path) {
34
+ return (_jsx(JsonViewSchemaValueCell, { sourcePath: path, compact: compact, descriptor: descriptor, inputKey: String(path[path.length - 1] ?? label), label: descriptor.title ?? label, onClear: atomicEditing ? () => atomicEditing.clear(path) : undefined, onCommit: atomicEditing ? (nextValue) => atomicEditing.commit(path, nextValue) : undefined, onOpen: navigateTo, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, saving: atomicEditing?.saving, value: value }));
35
+ }
36
+ const editable = atomicEditing !== undefined && path !== null && isEditableAtomicValue(value);
37
+ if (!editable || !atomicEditing || path === null)
38
+ return _jsx(ValueCell, { sourcePath: path ?? undefined, value: value, onOpen: navigateTo });
39
+ return (_jsx(AtomicValueEditor, { actions: _jsx(ExpandValueButton, { onClick: navigateTo }), label: label, multiline: typeof value === 'string' && value.includes('\n'), onCommit: (nextValue) => atomicEditing.commit(path, nextValue), saving: atomicEditing.saving, value: value, children: _jsx(ValueCellContent, { sourcePath: path ?? undefined, value: value, onOpen: navigateTo }) }));
40
+ }
41
+ function NodeView({ node, navigateTo, filePath, currentPath, currentJsonPath, currentLabel, renderAsMarkdown, getTableCellPosition, setTableCellPosition, renderMarkdown, uiSize, fillHeight = false, atomicEditing, schemaForPath, renderExternalWidget, onVisibleSourcePathsChange, }) {
42
+ const sourceLiteral = useJsonSourceLiterals()(currentJsonPath);
43
+ // A map of similarly-shaped records renders as a flat table (key column +
44
+ // the nested fields); any other object falls back to the key/value view.
45
+ const dictTableInfo = useMemo(() => (isPlainObject(node) ? getDictOfObjectsInfo(node) : null), [node]);
46
+ if (Array.isArray(node)) {
47
+ return (_jsx(ArrayView, { arr: node, navigateTo: navigateTo, filePath: filePath, currentPath: currentPath, currentJsonPath: currentJsonPath, getTableCellPosition: getTableCellPosition, setTableCellPosition: setTableCellPosition, renderMarkdown: renderMarkdown, uiSize: uiSize, fillHeight: fillHeight, atomicEditing: atomicEditing, schemaForPath: schemaForPath, renderExternalWidget: renderExternalWidget, onVisibleSourcePathsChange: onVisibleSourcePathsChange }));
48
+ }
49
+ if (dictTableInfo) {
50
+ return (_jsx(DictObjectTableView, { info: dictTableInfo, navigateTo: navigateTo, filePath: filePath, currentPath: currentPath, currentJsonPath: currentJsonPath, getTableCellPosition: getTableCellPosition, setTableCellPosition: setTableCellPosition, renderMarkdown: renderMarkdown, uiSize: uiSize, fillHeight: fillHeight, atomicEditing: atomicEditing, schemaForPath: schemaForPath, renderExternalWidget: renderExternalWidget, onVisibleSourcePathsChange: onVisibleSourcePathsChange }));
51
+ }
52
+ if (isPlainObject(node)) {
53
+ return _jsx(ObjectView, { obj: node, navigateTo: navigateTo, currentJsonPath: currentJsonPath, atomicEditing: atomicEditing, renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, schemaForPath: schemaForPath });
54
+ }
55
+ if (atomicEditing?.canReplace?.(currentJsonPath) === false)
56
+ atomicEditing = undefined;
57
+ const currentDescriptor = schemaForPath?.(currentJsonPath);
58
+ if (currentDescriptor) {
59
+ return (_jsx(JsonViewSchemaValueCell, { sourcePath: currentJsonPath, descriptor: currentDescriptor, label: currentDescriptor.title ?? currentLabel, onClear: atomicEditing ? () => atomicEditing.clear(currentJsonPath) : undefined, onCommit: atomicEditing ? (nextValue) => atomicEditing.commit(currentJsonPath, nextValue) : undefined, onOpen: () => undefined, inputKey: String(currentJsonPath[currentJsonPath.length - 1] ?? currentLabel), renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, saving: atomicEditing?.saving, value: node }));
60
+ }
61
+ if (typeof node === 'string') {
62
+ const content = (_jsx(ExpandedStringValue, { value: node, renderAsMarkdown: renderAsMarkdown, renderMarkdown: renderMarkdown }));
63
+ return atomicEditing ? (_jsx(AtomicValueEditor, { label: currentLabel, multiline: node.includes('\n'), onCommit: (nextValue) => atomicEditing.commit(currentJsonPath, nextValue), saving: atomicEditing.saving, value: node, children: content })) : content;
64
+ }
65
+ const content = (_jsx("div", { className: "whitespace-pre-wrap text-sm text-foreground", style: { overflowWrap: 'anywhere' }, children: sourceLiteral !== undefined ? _jsx(JsonSourceNumber, { literal: sourceLiteral }) : String(node) }));
66
+ return atomicEditing && isEditableAtomicValue(node) ? (_jsx(AtomicValueEditor, { label: currentLabel, onCommit: (nextValue) => atomicEditing.commit(currentJsonPath, nextValue), saving: atomicEditing.saving, value: node, children: content })) : content;
67
+ }
68
+ function ExpandedStringValue({ value, renderAsMarkdown, renderMarkdown, }) {
69
+ return renderAsMarkdown && renderMarkdown ? (renderMarkdown(value)) : (_jsx("div", { className: "whitespace-pre-wrap text-sm text-foreground", style: { overflowWrap: 'anywhere' }, children: value }));
70
+ }
71
+ function getTabularInfo(arr) {
72
+ if (arr.length === 0)
73
+ return null;
74
+ const first = arr[0];
75
+ // Check if all items are arrays (tuples) with consistent length
76
+ if (Array.isArray(first)) {
77
+ const tupleLength = first.length;
78
+ if (tupleLength === 0)
79
+ return null;
80
+ // Verify all items are arrays with the same length and contain primitives
81
+ for (const item of arr) {
82
+ if (!Array.isArray(item))
83
+ return null;
84
+ if (item.length !== tupleLength)
85
+ return null;
86
+ // Check that tuple contains only primitives (not nested objects/arrays)
87
+ for (const val of item) {
88
+ if (val !== null && typeof val === 'object')
89
+ return null;
90
+ }
91
+ }
92
+ return { type: 'tuples', length: tupleLength };
93
+ }
94
+ if (!isJsonViewTableCandidate(arr))
95
+ return null;
96
+ // Collect all unique keys, preserving order of first appearance
97
+ const seenKeys = new Set();
98
+ const columns = [];
99
+ for (const item of arr) {
100
+ for (const key of Object.keys(item)) {
101
+ if (!seenKeys.has(key)) {
102
+ seenKeys.add(key);
103
+ columns.push(key);
104
+ }
105
+ }
106
+ }
107
+ // Only show as table if we have at least one column
108
+ return columns.length > 0 ? { type: 'objects', columns } : null;
109
+ }
110
+ function ArrayView({ arr, navigateTo, filePath, currentPath, currentJsonPath, getTableCellPosition, setTableCellPosition, uiSize, fillHeight = false, atomicEditing, schemaForPath, renderMarkdown, renderExternalWidget, onVisibleSourcePathsChange, }) {
111
+ const sourceLiteral = useJsonSourceLiterals();
112
+ const getCellSourcePath = useCallback((rowIndex, column) => [...currentJsonPath, rowIndex, column], [currentJsonPath]);
113
+ if (arr.length === 0)
114
+ return _jsx("div", { className: "text-muted-foreground italic", children: "(empty list)" });
115
+ const tabularInfo = getTabularInfo(arr);
116
+ if (tabularInfo?.type === 'objects') {
117
+ const records = arr;
118
+ const titleKey = inferJsonViewRecordTitleKey(records) ?? tabularInfo.columns[0];
119
+ return (_jsx(TabularObjectArrayView, { arr: records, getCellSourcePath: getCellSourcePath, columns: [titleKey, ...tabularInfo.columns.filter((column) => column !== titleKey)], filePath: filePath, viewStateKey: currentPath, onOpenCell: ({ rowIndex, column }) => {
120
+ setTableCellPosition?.(currentPath, { row: rowIndex, col: column });
121
+ navigateTo(`${rowIndex}.${column}`, [rowIndex, column]);
122
+ }, initialCell: getTableCellPosition?.(currentPath), uiSize: uiSize, fillHeight: fillHeight, onVisibleRowsChange: (indices, filtered) => onVisibleSourcePathsChange?.(filtered
123
+ ? indices.map((index) => [...currentJsonPath, index])
124
+ : undefined), recordNavigation: {
125
+ titleColumn: titleKey,
126
+ getLabel: (rowIndex) => sourceLiteral([...currentJsonPath, rowIndex, titleKey])
127
+ ?? String(records[rowIndex][titleKey] ?? `Item ${rowIndex + 1}`),
128
+ onOpen: (rowIndex) => navigateTo(String(rowIndex), [rowIndex], { titleKey }),
129
+ renderTitle: ({ value, rowIndex }) => {
130
+ const path = [...currentJsonPath, rowIndex, titleKey];
131
+ const descriptor = schemaForPath?.(path);
132
+ return descriptor
133
+ ? _jsx(JsonViewSchemaValueDisplay, { compact: true, decorative: true, passive: true, descriptor: descriptor, onOpen: () => undefined, renderMarkdown: renderMarkdown, sourcePath: path, value: value })
134
+ : value === undefined
135
+ ? _jsx("span", { className: "italic text-muted-foreground", children: "Untitled" })
136
+ : _jsx(ValueCellContent, { containerInteractive: false, sourcePath: path, value: value, onOpen: () => undefined });
137
+ },
138
+ }, renderCell: ({ value, rowIndex, column, open }) => (_jsx(GeneralJsonValueCell, { atomicEditing: atomicEditing, compact: true, label: column, navigateTo: open, path: [...currentJsonPath, rowIndex, column], renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, schemaForPath: schemaForPath, value: value })), onDeleteRows: atomicEditing
139
+ ? (rowIndices) => atomicEditing.removeMany(rowIndices.map((rowIndex) => [...currentJsonPath, rowIndex]))
140
+ : undefined, rowDeletionDisabled: atomicEditing?.saving }));
141
+ }
142
+ if (tabularInfo?.type === 'tuples') {
143
+ return (_jsx(TupleArrayView, { arr: arr, tupleLength: tabularInfo.length, navigateTo: navigateTo, filePath: filePath, arrayPath: currentPath, currentJsonPath: currentJsonPath, uiSize: uiSize, fillHeight: fillHeight, atomicEditing: atomicEditing, schemaForPath: schemaForPath, renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, onVisibleSourcePathsChange: onVisibleSourcePathsChange }));
144
+ }
145
+ return (_jsx(SingleColumnArrayView, { arr: arr, navigateTo: navigateTo, filePath: filePath, arrayPath: currentPath, currentJsonPath: currentJsonPath, uiSize: uiSize, fillHeight: fillHeight, atomicEditing: atomicEditing, schemaForPath: schemaForPath, renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, onVisibleSourcePathsChange: onVisibleSourcePathsChange }));
146
+ }
147
+ function TupleArrayView({ arr, tupleLength, navigateTo, filePath, arrayPath, currentJsonPath, uiSize, fillHeight = false, atomicEditing, schemaForPath, renderMarkdown, renderExternalWidget, onVisibleSourcePathsChange, }) {
148
+ const getCellSourcePath = useCallback((rowIndex, column) => [...currentJsonPath, rowIndex, Number(column)], [currentJsonPath]);
149
+ const columns = useMemo(() => Array.from({ length: tupleLength }, (_, i) => String(i)), [tupleLength]);
150
+ const records = useMemo(() => arr.map((tuple) => tuple.reduce((record, value, index) => {
151
+ record[String(index)] = value;
152
+ return record;
153
+ }, {})), [arr]);
154
+ return (_jsx(TabularObjectArrayView, { arr: records, getCellSourcePath: getCellSourcePath, columns: columns, filePath: filePath, viewStateKey: arrayPath, onOpenCell: ({ rowIndex, column }) => {
155
+ navigateTo(`${rowIndex}.${column}`, [rowIndex, Number(column)]);
156
+ }, uiSize: uiSize, fillHeight: fillHeight, onVisibleRowsChange: (indices, filtered) => onVisibleSourcePathsChange?.(filtered
157
+ ? indices.map((index) => [...currentJsonPath, index])
158
+ : undefined), renderCell: ({ value, rowIndex, column, open }) => (_jsx(GeneralJsonValueCell, { atomicEditing: atomicEditing, compact: true, label: column, navigateTo: open, path: [...currentJsonPath, rowIndex, Number(column)], renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, schemaForPath: schemaForPath, value: value })), onDeleteRows: atomicEditing
159
+ ? (rowIndices) => atomicEditing.removeMany(rowIndices.map((rowIndex) => [...currentJsonPath, rowIndex]))
160
+ : undefined, rowDeletionDisabled: atomicEditing?.saving }));
161
+ }
162
+ function SingleColumnArrayView({ arr, navigateTo, filePath, arrayPath, currentJsonPath, uiSize, fillHeight = false, atomicEditing, schemaForPath, renderMarkdown, renderExternalWidget, onVisibleSourcePathsChange, }) {
163
+ const getCellSourcePath = useCallback((rowIndex) => [...currentJsonPath, rowIndex], [currentJsonPath]);
164
+ const records = useMemo(() => arr.map((item) => ({ Value: item })), [arr]);
165
+ return (_jsx(TabularObjectArrayView, { arr: records, getCellSourcePath: getCellSourcePath, columns: ['Value'], filePath: filePath, viewStateKey: arrayPath, onOpenCell: ({ rowIndex }) => navigateTo(String(rowIndex), [rowIndex]), uiSize: uiSize, fillHeight: fillHeight, onVisibleRowsChange: (indices, filtered) => onVisibleSourcePathsChange?.(filtered
166
+ ? indices.map((index) => [...currentJsonPath, index])
167
+ : undefined), renderCell: ({ value, rowIndex, open }) => (_jsx(GeneralJsonValueCell, { atomicEditing: atomicEditing, compact: true, label: "Value", navigateTo: open, path: [...currentJsonPath, rowIndex], renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, schemaForPath: schemaForPath, value: value })), onDeleteRows: atomicEditing
168
+ ? (rowIndices) => atomicEditing.removeMany(rowIndices.map((rowIndex) => [...currentJsonPath, rowIndex]))
169
+ : undefined, rowDeletionDisabled: atomicEditing?.saving }));
170
+ }
171
+ function DictObjectTableView({ info, navigateTo, filePath, currentPath, currentJsonPath, getTableCellPosition, setTableCellPosition, uiSize, fillHeight = false, atomicEditing, schemaForPath, renderMarkdown, renderExternalWidget, onVisibleSourcePathsChange, }) {
172
+ const getCellSourcePath = useCallback((rowIndex, column) => column === info.keyColumn ? undefined : [...currentJsonPath, info.keys[rowIndex], column], [currentJsonPath, info]);
173
+ // Rows are keyed by their original map key (not row index). The key column
174
+ // drills into the whole record (`obj[key]`); every other column drills into
175
+ // `obj[key].<field>`. Keying both the node and the path off the map key means
176
+ // a click and a later replay land on the same node.
177
+ // The key column shows the map key as text but has no field of its own, so a
178
+ // click drills into the record object rather than the (dead-end) key string.
179
+ return (_jsx(TabularObjectArrayView, { arr: info.records, getCellSourcePath: getCellSourcePath, columns: info.columns, filePath: filePath, viewStateKey: currentPath, onOpenCell: ({ rowIndex, column }) => {
180
+ const key = info.keys[rowIndex];
181
+ const navigationKey = column === info.keyColumn ? key : `${key}.${column}`;
182
+ const relativePath = column === info.keyColumn ? [key] : [key, column];
183
+ setTableCellPosition?.(currentPath, { row: rowIndex, col: column });
184
+ navigateTo(navigationKey, relativePath);
185
+ }, initialCell: getTableCellPosition?.(currentPath), uiSize: uiSize, fillHeight: fillHeight, onVisibleRowsChange: (indices, filtered) => onVisibleSourcePathsChange?.(filtered
186
+ ? indices.map((index) => [...currentJsonPath, info.keys[index]])
187
+ : undefined), recordNavigation: {
188
+ titleColumn: info.keyColumn,
189
+ getLabel: (rowIndex) => info.keys[rowIndex],
190
+ onOpen: (rowIndex) => navigateTo(info.keys[rowIndex], [info.keys[rowIndex]], {}),
191
+ renderTitle: ({ value }) => _jsx("span", { className: "min-w-0 truncate", children: String(value) }),
192
+ }, renderCell: ({ value, rowIndex, column, open }) => (_jsx(GeneralJsonValueCell, { atomicEditing: atomicEditing, compact: true, label: column, navigateTo: open, path: column === info.keyColumn ? null : [...currentJsonPath, info.keys[rowIndex], column], renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, schemaForPath: schemaForPath, value: value })), onDeleteRows: atomicEditing
193
+ ? (rowIndices) => atomicEditing.removeMany(rowIndices.map((rowIndex) => [...currentJsonPath, info.keys[rowIndex]]))
194
+ : undefined, rowDeletionDisabled: atomicEditing?.saving }));
195
+ }
196
+ function ObjectView({ obj, navigateTo, currentJsonPath, atomicEditing, renderMarkdown, renderExternalWidget, schemaForPath }) {
197
+ return (
198
+ // `shrink-0`: when the viewer fills a bounded viewport its scroller is a
199
+ // flex column, and this frame is a scroll container, so its `min-height:
200
+ // auto` resolves to 0 — without the guard a tall object is squeezed and
201
+ // clipped with no scrollbar of its own.
202
+ _jsx("div", { className: cn(TABULAR_DATA_FRAME_CLASS, 'shrink-0'), children: _jsx("div", { className: "@container max-w-full overflow-x-auto overscroll-x-none [word-break:normal] [overflow-wrap:normal]", children: _jsx("table", { className: "mb-0 w-full table-fixed border-separate border-spacing-0 text-sm", children: _jsx("tbody", { children: Object.entries(obj).map(([k, v], i, arr) => (_jsxs("tr", { children: [_jsx("th", { className: `w-[6rem] @[208px]:w-[45%] @[432px]:w-[12rem] border-r border-border bg-background px-4 py-1.5 text-left align-top font-normal text-muted-foreground ${i < arr.length - 1 ? 'border-b' : ''}`, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "flex min-h-7 items-center leading-5", children: _jsx("span", { className: "block min-w-0 truncate", children: k }) }) }), _jsx(TooltipContent, { side: "top", children: k })] }) }), _jsx("td", { className: `min-w-0 overflow-hidden break-words px-4 py-1.5 align-top transition-colors hover:bg-accent/50 ${i < arr.length - 1 ? 'border-b border-border' : ''}`, children: k === 'content'
203
+ && typeof v === 'string'
204
+ && renderMarkdown
205
+ && !schemaForPath?.([...currentJsonPath, k]) ? (() => {
206
+ const open = () => navigateTo(k, [k]);
207
+ const expandAction = _jsx(ExpandValueButton, { onClick: open });
208
+ const content = renderMarkdown(v);
209
+ const jsonPath = [...currentJsonPath, k];
210
+ if (!atomicEditing || atomicEditing.canReplace?.(jsonPath) === false) {
211
+ return (_jsx(StructuredValueCellFrame, { actions: expandAction, children: content }));
212
+ }
213
+ return (_jsx(AtomicValueEditor, { actions: expandAction, label: k, multiline: true, onCommit: (nextValue) => atomicEditing.commit(jsonPath, nextValue), saving: atomicEditing.saving, value: v, children: content }));
214
+ })() : (_jsx(GeneralJsonValueCell, { atomicEditing: atomicEditing, label: k, navigateTo: () => navigateTo(k, [k]), path: [...currentJsonPath, k], renderMarkdown: renderMarkdown, renderExternalWidget: renderExternalWidget, schemaForPath: schemaForPath, value: v })) })] }, k))) }) }) }) }));
215
+ }
@@ -0,0 +1 @@
1
+ export declare function copyTextToClipboard(value: string): Promise<void>;