@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,203 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import { basicSetup } from 'codemirror';
4
+ import { json } from '@codemirror/lang-json';
5
+ import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
6
+ import { Compartment, EditorState, StateEffect, StateField, Transaction } from '@codemirror/state';
7
+ import { Decoration, EditorView } from '@codemirror/view';
8
+ import { tags } from '@lezer/highlight';
9
+ import { AlignLeft, ArrowLeft, Code2 } from 'lucide-react';
10
+ import { useJsonViewsDevice } from '../browser-device.js';
11
+ import { cn } from '../lib/cn.js';
12
+ import { ConditionalTooltip } from '../primitives/tooltip.js';
13
+ const SOURCE_BUTTON_CLASS = 'grid h-7 w-7 shrink-0 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring active:scale-[0.97]';
14
+ export function SourceToggle({ active, format, onToggle, disabled = false }) {
15
+ const action = active ? 'Hide' : 'Show';
16
+ return (_jsx(ConditionalTooltip, { enabled: disabled, label: `${format} must be valid before switching to views.`, children: _jsx("span", { className: "inline-flex shrink-0", tabIndex: disabled ? 0 : undefined, children: _jsx("button", { type: "button", "data-id": "jsonView-source-toggle", "aria-label": `${action} ${format} source`, title: disabled ? undefined : `${action} ${format} source`, disabled: disabled, "aria-pressed": active, className: cn(SOURCE_BUTTON_CLASS, 'disabled:pointer-events-none disabled:opacity-50', active && 'bg-accent text-foreground'), onClick: onToggle, children: _jsx(Code2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }) }) }));
17
+ }
18
+ export const setSourceTargetRanges = StateEffect.define();
19
+ function sourceTargetSet(ranges, length) {
20
+ return Decoration.set(ranges.flatMap((range) => range.start >= 0 && range.start < range.end && range.end <= length
21
+ ? [Decoration.mark({ class: 'cm-source-target', attributes: { 'data-id': 'jsonView-source-target' } }).range(range.start, range.end)]
22
+ : []), true);
23
+ }
24
+ export const sourceTargetDecorations = StateField.define({
25
+ create: () => Decoration.none,
26
+ update: (decorations, transaction) => {
27
+ let next = decorations.map(transaction.changes);
28
+ for (const effect of transaction.effects) {
29
+ if (effect.is(setSourceTargetRanges))
30
+ next = sourceTargetSet(effect.value, transaction.state.doc.length);
31
+ }
32
+ return next;
33
+ },
34
+ provide: (field) => EditorView.decorations.from(field),
35
+ });
36
+ const jsonHighlightStyle = HighlightStyle.define([
37
+ { tag: tags.propertyName, color: 'var(--jv-source-key)' },
38
+ { tag: tags.string, color: 'var(--jv-source-string)' },
39
+ { tag: tags.number, color: 'var(--jv-source-number)' },
40
+ { tag: [tags.bool, tags.null], color: 'var(--jv-source-literal)' },
41
+ { tag: [tags.brace, tags.squareBracket, tags.separator], color: 'var(--jv-muted-foreground)' },
42
+ ]);
43
+ function sourceEditorTheme(fontSize) {
44
+ return EditorView.theme({
45
+ '&': {
46
+ height: '100%',
47
+ minHeight: '100%',
48
+ backgroundColor: 'transparent',
49
+ color: 'var(--jv-foreground)',
50
+ fontSize: `${fontSize}px`,
51
+ },
52
+ '&.cm-focused': { outline: 'none' },
53
+ '.cm-scroller': {
54
+ overflow: 'auto',
55
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
56
+ lineHeight: `${fontSize * 1.625}px`,
57
+ },
58
+ '.cm-content': {
59
+ minHeight: '100%',
60
+ padding: '12px 0',
61
+ caretColor: 'var(--jv-foreground)',
62
+ },
63
+ '.cm-cursorLayer .cm-cursor, .cm-dropCursor': {
64
+ borderLeftColor: 'var(--jv-foreground) !important',
65
+ borderLeftWidth: '2px !important',
66
+ },
67
+ '.cm-line': { padding: '0 12px 0 6px' },
68
+ '.cm-gutters': {
69
+ backgroundColor: 'transparent',
70
+ color: 'var(--jv-muted-foreground)',
71
+ border: 'none',
72
+ paddingLeft: '4px',
73
+ },
74
+ '.cm-gutterElement': { padding: '0 3px' },
75
+ '.cm-foldGutter .cm-gutterElement': {
76
+ display: 'flex',
77
+ alignItems: 'center',
78
+ justifyContent: 'center',
79
+ padding: '0 2px',
80
+ },
81
+ '.cm-foldGutter .cm-gutterElement > span': {
82
+ display: 'inline-flex',
83
+ width: '12px',
84
+ height: '12px',
85
+ alignItems: 'center',
86
+ justifyContent: 'center',
87
+ lineHeight: '1',
88
+ transform: 'translateY(-1px)',
89
+ },
90
+ '.cm-activeLine, .cm-activeLineGutter': {
91
+ backgroundColor: 'color-mix(in srgb, var(--jv-accent) 45%, transparent)',
92
+ },
93
+ '.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
94
+ backgroundColor: 'color-mix(in srgb, var(--jv-primary) 22%, transparent)',
95
+ },
96
+ '.cm-foldPlaceholder': {
97
+ backgroundColor: 'var(--jv-muted)',
98
+ color: 'var(--jv-muted-foreground)',
99
+ border: '1px solid var(--jv-border)',
100
+ },
101
+ '.cm-source-target': {
102
+ backgroundColor: 'color-mix(in srgb, var(--jv-primary) 20%, transparent)',
103
+ borderRadius: '2px',
104
+ },
105
+ });
106
+ }
107
+ function CodeMirrorSource({ content, format, onChange, readOnly, targetRanges, syntaxDisabled, }) {
108
+ const device = useJsonViewsDevice();
109
+ const mount = useRef(null);
110
+ const view = useRef(undefined);
111
+ const applyingExternalContent = useRef(false);
112
+ const onChangeRef = useRef(onChange);
113
+ const languageCompartment = useMemo(() => new Compartment(), []);
114
+ const readOnlyCompartment = useMemo(() => new Compartment(), []);
115
+ const themeCompartment = useMemo(() => new Compartment(), []);
116
+ const targetKey = JSON.stringify(targetRanges);
117
+ const revealedTarget = useRef(false);
118
+ onChangeRef.current = onChange;
119
+ useLayoutEffect(() => {
120
+ if (!mount.current)
121
+ return;
122
+ const extensions = [
123
+ basicSetup,
124
+ sourceTargetDecorations,
125
+ syntaxHighlighting(jsonHighlightStyle),
126
+ EditorState.tabSize.of(2),
127
+ EditorView.editorAttributes.of({ 'data-id': 'jsonView-source-editor' }),
128
+ EditorView.contentAttributes.of({ 'aria-label': `${format} source`, spellcheck: 'false' }),
129
+ readOnlyCompartment.of([EditorState.readOnly.of(readOnly), EditorView.editable.of(!readOnly)]),
130
+ themeCompartment.of(sourceEditorTheme(device === 'mobile' ? 16 : 12)),
131
+ EditorView.updateListener.of((update) => {
132
+ if (update.docChanged && !applyingExternalContent.current)
133
+ onChangeRef.current(update.state.doc.toString());
134
+ }),
135
+ ];
136
+ extensions.push(languageCompartment.of(format === 'JSON' && !syntaxDisabled ? json() : []));
137
+ view.current = new EditorView({
138
+ state: EditorState.create({ doc: content, extensions }),
139
+ parent: mount.current,
140
+ });
141
+ return () => {
142
+ view.current?.destroy();
143
+ view.current = undefined;
144
+ };
145
+ }, []);
146
+ useLayoutEffect(() => {
147
+ view.current?.dispatch({ effects: languageCompartment.reconfigure(format === 'JSON' && !syntaxDisabled ? json() : []) });
148
+ }, [format, syntaxDisabled, languageCompartment]);
149
+ useLayoutEffect(() => {
150
+ const editor = view.current;
151
+ if (!editor || editor.state.doc.toString() === content)
152
+ return;
153
+ applyingExternalContent.current = true;
154
+ editor.dispatch({
155
+ changes: { from: 0, to: editor.state.doc.length, insert: content },
156
+ effects: editor.scrollSnapshot(),
157
+ annotations: Transaction.addToHistory.of(false),
158
+ });
159
+ applyingExternalContent.current = false;
160
+ }, [content]);
161
+ useLayoutEffect(() => {
162
+ view.current?.dispatch({
163
+ effects: readOnlyCompartment.reconfigure([EditorState.readOnly.of(readOnly), EditorView.editable.of(!readOnly)]),
164
+ });
165
+ }, [readOnly, readOnlyCompartment]);
166
+ useLayoutEffect(() => {
167
+ view.current?.dispatch({
168
+ effects: themeCompartment.reconfigure(sourceEditorTheme(device === 'mobile' ? 16 : 12)),
169
+ });
170
+ }, [device, themeCompartment]);
171
+ useLayoutEffect(() => {
172
+ const editor = view.current;
173
+ if (!editor)
174
+ return;
175
+ editor.dispatch({ effects: setSourceTargetRanges.of(targetRanges) });
176
+ }, [targetKey]);
177
+ useEffect(() => {
178
+ const editor = view.current;
179
+ const first = targetRanges[0];
180
+ if (!editor || !first || revealedTarget.current)
181
+ return;
182
+ const timer = setTimeout(() => {
183
+ if (view.current !== editor || revealedTarget.current)
184
+ return;
185
+ revealedTarget.current = true;
186
+ const lineTop = editor.lineBlockAt(first.start).top;
187
+ const viewportHeight = editor.scrollDOM.clientHeight;
188
+ editor.scrollDOM.scrollTop = Math.max(0, lineTop - viewportHeight * 0.35);
189
+ }, 0);
190
+ return () => clearTimeout(timer);
191
+ }, [targetKey]);
192
+ return _jsx("div", { ref: mount, className: "h-full min-h-full w-full" });
193
+ }
194
+ export function SourceFormatButton({ format, disabled, onFormat }) {
195
+ return _jsx("button", { type: "button", "data-id": "jsonView-source-format", "aria-label": `Format ${format}`, title: `Format ${format}`, className: cn(SOURCE_BUTTON_CLASS, 'disabled:pointer-events-none disabled:opacity-50'), disabled: disabled, onClick: onFormat, children: _jsx(AlignLeft, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) });
196
+ }
197
+ export function SourceEditor({ embedded = false, content, dirty, fillHeight, format, invalidSourceError, saveStatus, onChange, onClose, onFormat, readOnly, saveError, saving, targetRanges = [], }) {
198
+ const error = invalidSourceError ?? saveError;
199
+ const [targetDismissed, setTargetDismissed] = useState(false);
200
+ const visibleTargetRanges = targetDismissed ? [] : targetRanges;
201
+ const dismissTarget = useCallback(() => setTargetDismissed(true), []);
202
+ return (_jsx("div", { "data-id": "jsonView-source-view", className: cn('flex min-h-0 flex-1 flex-col bg-background text-foreground', fillHeight && 'h-full'), onPointerDownCapture: dismissTarget, onClickCapture: dismissTarget, children: _jsxs("div", { className: cn('mx-auto flex min-h-0 w-full flex-1 flex-col', !embedded && 'max-w-6xl px-3 py-4 sm:px-6 sm:py-5', fillHeight && 'h-full'), children: [!embedded && _jsxs("div", { "data-id": "json-viewer-toolbar", className: "relative flex min-h-8 shrink-0 items-center gap-2", children: [!embedded && _jsx(ConditionalTooltip, { enabled: Boolean(invalidSourceError), label: `${format} must be valid before switching to views.`, children: _jsx("span", { className: "mr-auto inline-flex", tabIndex: invalidSourceError ? 0 : undefined, children: _jsxs("button", { type: "button", "data-id": "jsonView-source-views", className: "disabled:pointer-events-none disabled:opacity-50 mr-auto flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", disabled: Boolean(invalidSourceError), onClick: onClose, children: [_jsx(ArrowLeft, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), " Views"] }) }) }), onFormat && _jsx(SourceFormatButton, { format: format, disabled: Boolean(invalidSourceError) || saving, onFormat: onFormat }), !saveStatus && _jsx("span", { className: cn('min-w-12 text-right text-xs text-muted-foreground', error && 'text-destructive'), role: "status", children: invalidSourceError ? `Invalid ${format}` : saveError ? 'Save failed' : saving ? 'Saving…' : readOnly ? 'Read only' : dirty ? 'Unsaved changes' : 'Saved' }), !embedded && _jsx(SourceToggle, { active: true, format: format, onToggle: onClose, disabled: Boolean(invalidSourceError) }), !embedded && saveStatus] }), !saveStatus && error && _jsx("p", { "data-id": "jsonView-source-error", role: error ? 'alert' : undefined, className: cn('flex shrink-0 items-center overflow-hidden text-ellipsis whitespace-nowrap text-xs text-destructive', error ? 'h-6' : 'h-1'), title: error ?? undefined, children: error }), _jsx("div", { className: cn('relative mt-1 w-full flex-1 overflow-hidden rounded-md border border-border bg-background focus-within:ring-1 focus-within:ring-ring', fillHeight ? 'min-h-0' : 'min-h-96'), children: _jsx(CodeMirrorSource, { syntaxDisabled: invalidSourceError?.includes('nesting limit'), content: content, format: format, onChange: (next) => { dismissTarget(); onChange(next); }, readOnly: readOnly, targetRanges: visibleTargetRanges }) })] }) }));
203
+ }
@@ -0,0 +1,3 @@
1
+ import { type JsonViewerProps } from './view-types.js';
2
+ export declare function JsonViewer({ compiled, defaultKey, editing, filePath, fillHeight, onCurrentPathChange, renderExternalWidget, renderMarkdown, sourceControl, sourceContent, sourceVisible, onSourceVisibleChange, uiSize, viewsEnabled, tabularRoot, }: JsonViewerProps): import("react").JSX.Element;
3
+ export type { JsonViewJsonEditing } from './view-types.js';
@@ -0,0 +1,322 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HtmlView } from './html-view.js';
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { Braces, LayoutGrid, Columns3, FileText, Search, Table2 } from 'lucide-react';
5
+ import { useJsonViewsRegistries } from '../widget-registry.js';
6
+ import { cn } from '../lib/cn.js';
7
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '../primitives/tabs.js';
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip.js';
9
+ import {} from '@script-it/json-views-core';
10
+ import { compileJsonViewMetadata, schemaForJsonViewPath } from '@script-it/json-views-core';
11
+ import {} from './schema-value.js';
12
+ import { JsonViewAddView, JsonViewViewSettings } from './view-settings.js';
13
+ import { GeneralJsonView } from './viewers/general/general-json-view.js';
14
+ import {} from './view-types.js';
15
+ import { isRecordCollection, presentsRecordCollection, rawViewsFor, rawViewFor, uniqueViewName, uniqueViewId, jsonPathForValuePath, rowForPage } from './view-model.js';
16
+ import { Diagnostics } from './view-diagnostics.js';
17
+ import { GeneralRecordView, RecordView } from './record-view.js';
18
+ import { ProjectedTableView } from './table-view.js';
19
+ import { KanbanView } from './kanban-view.js';
20
+ import { useViewerState } from '../viewer-state.js';
21
+ const VIEW_TAB_CLASS = 'h-7 shrink-0 gap-1.5 rounded-md px-2 py-0 text-xs font-medium text-muted-foreground shadow-none hover:bg-accent hover:text-foreground data-[state=active]:bg-accent data-[state=active]:text-foreground data-[state=active]:shadow-none [&>svg]:shrink-0';
22
+ function slashPath(view) {
23
+ const segments = view.path.segments.map((segment) => {
24
+ if (segment.kind === 'property')
25
+ return segment.key;
26
+ if (segment.kind === 'index')
27
+ return String(segment.index);
28
+ return '*';
29
+ });
30
+ return `/${segments.join('/')}`;
31
+ }
32
+ function ViewTab({ children, id, path, value }) {
33
+ const [tooltipOpen, setTooltipOpen] = useState(false);
34
+ return (_jsxs(Tooltip, { open: tooltipOpen, onOpenChange: setTooltipOpen, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "inline-flex shrink-0", onBlur: () => setTooltipOpen(false), onFocus: (event) => {
35
+ if (event.target instanceof HTMLElement && event.target.matches(':focus-visible'))
36
+ setTooltipOpen(true);
37
+ }, children: _jsx(TabsTrigger, { value: value, "data-id": id, className: VIEW_TAB_CLASS, children: children }) }) }), _jsx(TooltipContent, { side: "bottom", className: "max-w-80 break-all font-mono", children: path })] }));
38
+ }
39
+ function isRecord(value) {
40
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
41
+ }
42
+ function isInferredSingleRecord(view) {
43
+ return view.declarationIndex < 0 && Array.isArray(view.value)
44
+ && view.value.length === 1 && isRecord(view.value[0]);
45
+ }
46
+ function metadataWithViews(compiled, views) {
47
+ const metadata = isRecord(compiled.metadata) ? compiled.metadata : {};
48
+ return {
49
+ ...metadata,
50
+ version: 1,
51
+ ...(metadata.schema === undefined && compiled.inference && Object.keys(compiled.inference.schema).length > 0
52
+ ? { schema: compiled.inference.schema }
53
+ : {}),
54
+ views,
55
+ };
56
+ }
57
+ function labelForPath(path, fallback) {
58
+ const last = path.at(-1);
59
+ if (last === undefined)
60
+ return fallback;
61
+ if (typeof last === 'number')
62
+ return `Item ${last + 1}`;
63
+ const words = last.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ').trim();
64
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : fallback;
65
+ }
66
+ function AdaptiveView({ forceTable = false, compiled, copyFileName, editing, filePath, fillHeight, onSaveView, onCurrentPathChange, query, rawView, renderExternalWidget, renderMarkdown, uiSize, view, }) {
67
+ const { widgets } = useJsonViewsRegistries();
68
+ const descriptor = schemaForJsonViewPath(compiled.schema, view.sourcePath)?.descriptor;
69
+ const registered = descriptor && (widgets.get(descriptor.type) || widgets.getDisplay(descriptor.type) || renderExternalWidget);
70
+ const singleInferredRecord = isInferredSingleRecord(view) && !forceTable && !registered;
71
+ if (singleInferredRecord)
72
+ return _jsx(GeneralRecordView, { path: [...view.sourcePath, 0], compiled: compiled, copyFileName: copyFileName, editing: editing, filePath: filePath, fillHeight: fillHeight, name: view.name, onCurrentPathChange: onCurrentPathChange, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, uiSize: uiSize });
73
+ const projected = presentsRecordCollection(view, forceTable) && !registered;
74
+ if (projected)
75
+ return _jsx(ProjectedTableView, { compiled: compiled, copyFileName: copyFileName, editing: editing, filePath: filePath, fillHeight: fillHeight, onCurrentPathChange: onCurrentPathChange, onSaveView: onSaveView, query: query, rawView: rawView, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, uiSize: uiSize, view: view });
76
+ return (_jsx(GeneralJsonView, { root: compiled.root, initialPath: view.sourcePath, defaultKey: view.name, filePath: `${filePath ?? 'json'}#view:${view.id}`, editing: editing, onCurrentPathChange: onCurrentPathChange, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, schemaForPath: (path) => schemaForJsonViewPath(compiled.schema, path)?.descriptor, uiSize: uiSize, fillHeight: fillHeight, renderRecord: (target) => (_jsx(GeneralRecordView, { ...target, compiled: compiled, copyFileName: copyFileName, editing: editing, filePath: filePath, fillHeight: fillHeight, name: view.name, onCurrentPathChange: onCurrentPathChange, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, uiSize: uiSize })) }));
77
+ }
78
+ function PageView({ compiled, copyFileName, editing, filePath, fillHeight, onCurrentPathChange, renderExternalWidget, renderMarkdown, uiSize, view, }) {
79
+ const row = rowForPage(view);
80
+ useEffect(() => {
81
+ onCurrentPathChange?.(view.sourcePath);
82
+ }, [onCurrentPathChange, view.sourcePath]);
83
+ if (!row)
84
+ return null;
85
+ return _jsx(RecordView, { compiled: compiled, copyFileName: copyFileName, editing: editing, filePath: filePath, fillHeight: fillHeight, onCurrentPathChange: onCurrentPathChange, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, row: row, uiSize: uiSize, view: view });
86
+ }
87
+ function ConfiguredView(props) {
88
+ if (props.view.display === 'html')
89
+ return _jsx(HtmlView, { ...props });
90
+ if (props.view.display === 'kanban')
91
+ return _jsx(KanbanView, { ...props });
92
+ if (props.view.declarationIndex >= 0
93
+ && props.view.value !== null
94
+ && typeof props.view.value === 'object'
95
+ && !Array.isArray(props.view.value)
96
+ && !isRecordCollection(props.view.value)) {
97
+ return _jsx(PageView, { ...props });
98
+ }
99
+ return _jsx(AdaptiveView, { ...props });
100
+ }
101
+ export function JsonViewer({ compiled, defaultKey, editing, filePath, fillHeight = false, onCurrentPathChange, renderExternalWidget, renderMarkdown, sourceControl, sourceContent, sourceVisible, onSourceVisibleChange, uiSize, viewsEnabled = true, tabularRoot = false, }) {
102
+ const { types } = useJsonViewsRegistries();
103
+ const viewerState = useViewerState();
104
+ const firstViewTab = viewsEnabled && compiled.views[0] ? `view:${compiled.views[0].id}` : 'root';
105
+ const initialActiveTab = viewerState.activeView ?? firstViewTab;
106
+ const [activeTab, setActiveTab] = useState(initialActiveTab);
107
+ const [queries, setQueries] = useState(() => viewerState.queries.toRecord());
108
+ const [searchOpenTab, setSearchOpenTab] = useState(() => queries[initialActiveTab] ? initialActiveTab : undefined);
109
+ const [currentPaths, setCurrentPaths] = useState({});
110
+ const [sourcePaths, setSourcePaths] = useState({});
111
+ const searchInput = useRef(null);
112
+ const usableTabs = useMemo(() => new Set(['root', ...(viewsEnabled ? compiled.views.map((view) => `view:${view.id}`) : [])]), [compiled.views, viewsEnabled]);
113
+ const structuredTab = usableTabs.has(activeTab) ? activeTab : firstViewTab;
114
+ const selectedTab = sourceVisible && sourceContent ? 'source' : structuredTab;
115
+ const rootView = useMemo(() => ({
116
+ id: '__root__',
117
+ name: defaultKey,
118
+ path: { source: '$', root: '$', segments: [] },
119
+ sourcePath: [],
120
+ value: compiled.root,
121
+ display: 'adaptive',
122
+ sort: [],
123
+ declarationIndex: -1,
124
+ metadataPath: [],
125
+ }), [compiled.root, defaultKey]);
126
+ const declaredView = viewsEnabled ? compiled.views.find((view) => `view:${view.id}` === structuredTab) : undefined;
127
+ const activeView = declaredView ?? rootView;
128
+ const rememberCurrentPath = useCallback((path, paths) => {
129
+ setCurrentPaths((current) => JSON.stringify(current[selectedTab]) === JSON.stringify(path)
130
+ ? current
131
+ : { ...current, [selectedTab]: path });
132
+ setSourcePaths((current) => JSON.stringify(current[selectedTab]) === JSON.stringify(paths)
133
+ ? current
134
+ : { ...current, [selectedTab]: paths });
135
+ }, [selectedTab]);
136
+ const currentPath = currentPaths[structuredTab] ?? activeView.sourcePath;
137
+ const currentPathKey = JSON.stringify(currentPath);
138
+ const sourcePathsKey = JSON.stringify(sourcePaths[selectedTab]);
139
+ const viewSaveMode = editing?.viewSaveMode ?? 'automatic';
140
+ const viewDraftKey = JSON.stringify([structuredTab, jsonPathForValuePath(currentPath)]);
141
+ const activeRawView = declaredView ? rawViewFor(compiled, declaredView) : undefined;
142
+ const rawViews = rawViewsFor(compiled);
143
+ const rootDraftRawView = useMemo(() => {
144
+ const name = uniqueViewName(labelForPath(currentPath, Array.isArray(compiled.root) ? 'Data' : defaultKey), rawViews);
145
+ return { id: uniqueViewId(name, rawViews), name, path: jsonPathForValuePath(currentPath) };
146
+ }, [compiled.root, currentPath, defaultKey, rawViews]);
147
+ const settingsRawView = activeRawView ?? rootDraftRawView;
148
+ const settingsDeclarationIndex = declaredView?.declarationIndex ?? rawViews.length;
149
+ const settingsView = useMemo(() => {
150
+ if (declaredView)
151
+ return declaredView;
152
+ const candidate = compileJsonViewMetadata(compiled.root, types, {
153
+ metadata: metadataWithViews(compiled, [...rawViews, rootDraftRawView]),
154
+ });
155
+ return candidate.views.find((view) => view.declarationIndex === rawViews.length);
156
+ }, [compiled, declaredView, rawViews, rootDraftRawView, types, types.version]);
157
+ const [previewState, setPreviewState] = useState(() => {
158
+ const cached = viewerState.viewDrafts.get(viewDraftKey);
159
+ return { key: viewDraftKey, ...(cached ? { value: cached } : {}) };
160
+ });
161
+ const previewRawView = previewState.key === viewDraftKey
162
+ ? previewState.value
163
+ : viewerState.viewDrafts.get(viewDraftKey);
164
+ const previewView = useMemo(() => {
165
+ if (!previewRawView)
166
+ return undefined;
167
+ const views = [...rawViews];
168
+ if (declaredView)
169
+ views[declaredView.declarationIndex] = previewRawView;
170
+ else
171
+ views.push(previewRawView);
172
+ const candidate = compileJsonViewMetadata(compiled.root, types, { metadata: metadataWithViews(compiled, views) });
173
+ return candidate.views.find((view) => view.declarationIndex === settingsDeclarationIndex);
174
+ }, [compiled, declaredView, previewRawView, rawViews, settingsDeclarationIndex, types, types.version]);
175
+ const previewSettings = useCallback((next) => {
176
+ setPreviewState((current) => current.key === viewDraftKey && JSON.stringify(current.value) === JSON.stringify(next)
177
+ ? current
178
+ : { key: viewDraftKey, ...(next ? { value: next } : {}) });
179
+ if (next)
180
+ viewerState.viewDrafts.set(viewDraftKey, next);
181
+ else
182
+ viewerState.viewDrafts.delete(viewDraftKey);
183
+ }, [viewDraftKey, viewerState]);
184
+ const renderedView = previewView ?? activeView;
185
+ const renderedRawView = previewView ? previewRawView : activeRawView;
186
+ const query = queries[selectedTab] ?? '';
187
+ const hasTabs = viewsEnabled && compiled.views.length > 0;
188
+ const addViewUnavailableReason = currentPath[0] === '$jsonviews'
189
+ ? '$jsonviews stores internal view configuration, so it cannot be saved as another view.'
190
+ : editing?.canEditViews === false
191
+ ? editing.viewCreationUnavailableReason ?? 'This document’s view metadata cannot be saved.'
192
+ : undefined;
193
+ const canAddViews = viewsEnabled && editing !== undefined && compiled.root !== null
194
+ && (typeof compiled.root === 'object' || compiled.metadataSource === 'external');
195
+ const canPersistViews = canAddViews && editing.canEditViews !== false;
196
+ const settingsUnavailableReason = !declaredView && currentPath[0] === '$jsonviews'
197
+ ? '$jsonviews stores internal view configuration, so it cannot be configured as a data view.'
198
+ : settingsView && (declaredView !== undefined || presentsRecordCollection(settingsView))
199
+ ? undefined
200
+ : 'This location uses the natural JSON layout, so it has no configurable table view.';
201
+ const forceTable = tabularRoot && renderedView.sourcePath.length === 0;
202
+ const searchable = renderedView.display !== 'html' && presentsRecordCollection(renderedView, forceTable);
203
+ const searchOpen = searchable && (searchOpenTab === selectedTab || query.length > 0);
204
+ const hasToolbar = hasTabs || searchable || compiled.diagnostics.length > 0
205
+ || canAddViews || sourceControl !== undefined
206
+ || (editing !== undefined && settingsRawView !== undefined);
207
+ useEffect(() => {
208
+ viewerState.setActiveView(selectedTab);
209
+ }, [selectedTab, viewerState]);
210
+ useEffect(() => {
211
+ if (selectedTab !== 'source')
212
+ onCurrentPathChange?.(currentPath, sourcePaths[selectedTab]);
213
+ }, [currentPathKey, sourcePathsKey, selectedTab, onCurrentPathChange]);
214
+ const persistViews = useCallback(async (views, requiredIndex) => {
215
+ if (!editing)
216
+ throw new Error('This JSON is read-only');
217
+ if (requiredIndex !== undefined) {
218
+ const candidate = compileJsonViewMetadata(compiled.root, types, { metadata: metadataWithViews(compiled, views) });
219
+ const valid = candidate.views.some((view) => view.declarationIndex === requiredIndex);
220
+ const issue = candidate.diagnostics.find((item) => item.scope === 'view' && item.metadataPath[2] === requiredIndex);
221
+ if (!valid)
222
+ throw new Error(issue?.message ?? 'The view is not valid for this JSON');
223
+ }
224
+ await editing.replaceViews(views);
225
+ }, [compiled, editing, types, types.version]);
226
+ const saveActiveView = useCallback(async (next) => {
227
+ if (!declaredView)
228
+ throw new Error('Select a saved view first');
229
+ const nextViews = [...rawViews];
230
+ nextViews[declaredView.declarationIndex] = next;
231
+ await persistViews(nextViews, declaredView.declarationIndex);
232
+ }, [declaredView, persistViews, rawViews]);
233
+ const saveSettingsView = useCallback(async (next) => {
234
+ if (declaredView) {
235
+ await saveActiveView(next);
236
+ }
237
+ else {
238
+ const nextViews = [...rawViews, next];
239
+ await persistViews(nextViews, nextViews.length - 1);
240
+ if (typeof next.id === 'string')
241
+ setActiveTab(`view:${next.id}`);
242
+ }
243
+ // An older save must not discard a newer working configuration.
244
+ if (JSON.stringify(viewerState.viewDrafts.get(viewDraftKey)) === JSON.stringify(next))
245
+ previewSettings(undefined);
246
+ }, [declaredView, persistViews, rawViews, saveActiveView, viewerState, viewDraftKey, previewSettings]);
247
+ const settingsDraftRawView = previewRawView
248
+ ? previewRawView
249
+ : settingsRawView;
250
+ const columnRawView = viewSaveMode === 'explicit'
251
+ ? settingsDraftRawView
252
+ : renderedRawView;
253
+ const saveColumnView = canPersistViews
254
+ ? viewSaveMode === 'explicit'
255
+ ? async (next) => { previewSettings(next); }
256
+ : declaredView && activeRawView
257
+ ? async (next) => {
258
+ previewSettings(next);
259
+ try {
260
+ await saveSettingsView(next);
261
+ }
262
+ finally {
263
+ // The document owns pending and failed writes once submitted.
264
+ if (JSON.stringify(viewerState.viewDrafts.get(viewDraftKey)) === JSON.stringify(next))
265
+ previewSettings(undefined);
266
+ }
267
+ }
268
+ : undefined
269
+ : undefined;
270
+ const addView = async (option) => {
271
+ const name = uniqueViewName(option.name, rawViews);
272
+ const id = uniqueViewId(name, rawViews);
273
+ const nextView = { id, name, path: option.path };
274
+ if (option.display === 'kanban' && option.groupBy) {
275
+ nextView.display = 'kanban';
276
+ nextView.groupBy = option.groupBy;
277
+ }
278
+ const nextViews = [...rawViews, nextView];
279
+ await persistViews(nextViews, nextViews.length - 1);
280
+ setActiveTab(`view:${id}`);
281
+ };
282
+ useEffect(() => {
283
+ if (searchOpen)
284
+ searchInput.current?.focus();
285
+ }, [searchOpen]);
286
+ const setQuery = (value) => {
287
+ setQueries((current) => ({ ...current, [selectedTab]: value }));
288
+ viewerState.queries.set(selectedTab, value);
289
+ };
290
+ const closeSearch = () => {
291
+ if (!query)
292
+ setSearchOpenTab(undefined);
293
+ };
294
+ return (_jsx(Tabs, { value: selectedTab, onValueChange: (value) => { if (value !== 'source')
295
+ setActiveTab(value); onSourceVisibleChange?.(value === 'source'); }, className: cn('flex min-h-0 flex-1 flex-col bg-background text-foreground', fillHeight && 'h-full'), children: _jsxs("div", { "data-id": "json-viewer-content", className: cn('flex min-h-0 w-full max-w-none flex-1 flex-col px-3 py-4 sm:px-6 sm:py-5', fillHeight && 'h-full'), children: [hasToolbar && (_jsxs("div", { "data-id": "json-viewer-toolbar", className: "relative flex min-h-8 shrink-0 flex-nowrap items-center gap-x-2", children: [_jsxs("div", { "data-id": "jsonView-toolbar-actions", className: "ml-auto flex h-8 shrink-0 items-center gap-1", children: [selectedTab !== 'source' && searchable && (searchOpen ? (_jsxs("label", { "data-id": "jsonView-search-control", className: "flex h-7 w-44 items-center overflow-hidden rounded-md border border-transparent bg-transparent transition-[width,background-color] focus-within:border-border focus-within:bg-card sm:w-52", children: [_jsx(Search, { className: "mx-1.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("input", { ref: searchInput, "aria-label": "Search rows", className: "h-full min-w-0 flex-1 bg-transparent pr-2 text-xs outline-none placeholder:text-muted-foreground", value: query, onBlur: closeSearch, onChange: (event) => setQuery(event.target.value), onKeyDown: (event) => {
296
+ if (event.key === 'Escape') {
297
+ setQuery('');
298
+ setSearchOpenTab(undefined);
299
+ }
300
+ }, placeholder: "Type to search\u2026" })] })) : (_jsx("button", { "data-id": "jsonView-search-button", type: "button", "aria-label": "Search rows", title: "Search", className: "grid h-7 w-7 shrink-0 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", onClick: () => setSearchOpenTab(selectedTab), children: _jsx(Search, { className: "h-3.5 w-3.5" }) }))), editing && settingsRawView && (_jsx(JsonViewViewSettings, { hasUnsavedChanges: previewRawView !== undefined, view: previewView ?? settingsView ?? activeView, rawView: activeView.display === 'html' ? activeRawView ?? settingsDraftRawView : settingsDraftRawView, schema: compiled.schema, disabled: editing.saving, saveMode: viewSaveMode, saveUnavailableReason: canPersistViews ? undefined : editing.viewCreationUnavailableReason, unavailableReason: settingsUnavailableReason, onPreview: previewSettings, onSave: saveSettingsView, onDuplicate: declaredView && activeRawView && canPersistViews ? async () => {
301
+ const name = uniqueViewName(`${declaredView.name} copy`, rawViews);
302
+ const id = uniqueViewId(name, rawViews);
303
+ const duplicate = { ...activeRawView, id, name };
304
+ const nextViews = [...rawViews];
305
+ const duplicateIndex = declaredView.declarationIndex + 1;
306
+ nextViews.splice(duplicateIndex, 0, duplicate);
307
+ await persistViews(nextViews, duplicateIndex);
308
+ setActiveTab(`view:${id}`);
309
+ } : undefined, onDelete: declaredView && canPersistViews ? async () => {
310
+ const nextViews = [...rawViews];
311
+ nextViews.splice(declaredView.declarationIndex, 1);
312
+ await persistViews(nextViews);
313
+ setActiveTab('root');
314
+ } : undefined }, typeof settingsDraftRawView.id === 'string' ? settingsDraftRawView.id : '__view_settings__')), sourceControl, _jsx(Diagnostics, { compiled: compiled })] }), (hasTabs || canAddViews || sourceContent) && (_jsxs("div", { "data-id": "jsonView-view-controls", className: "flex min-w-0 flex-1 items-center", children: [canAddViews && _jsx(JsonViewAddView, { align: "start", root: compiled.root, schema: compiled.schema, currentPath: jsonPathForValuePath(currentPath), disabled: editing.saving, onAdd: addView, unavailableReason: addViewUnavailableReason }, "add-view"), (hasTabs || sourceContent) && (_jsxs(TabsList, { "data-id": "jsonView-json-tabs", className: "h-8 min-w-0 flex-initial justify-start gap-0.5 overflow-x-auto overscroll-x-contain rounded-none bg-transparent p-0", children: [compiled.views.map((view) => {
315
+ const Icon = view.display === 'html' ? LayoutGrid : view.display === 'kanban'
316
+ ? Columns3
317
+ : isInferredSingleRecord(view) || (view.value !== null && typeof view.value === 'object' && !Array.isArray(view.value) && !isRecordCollection(view.value))
318
+ ? FileText
319
+ : Table2;
320
+ return _jsxs(ViewTab, { id: `jsonView-json-tab-${view.id}`, value: `view:${view.id}`, path: slashPath(view), children: [_jsx(Icon, { className: "h-3.5 w-3.5" }), view.name] }, view.id);
321
+ }), _jsxs(ViewTab, { id: "jsonView-json-tab-root", value: "root", path: isInferredSingleRecord(rootView) && !forceTable ? 'Root record' : 'Root table', children: [isInferredSingleRecord(rootView) && !forceTable ? _jsx(FileText, { className: "h-3.5 w-3.5" }) : _jsx(Table2, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "sr-only", children: isInferredSingleRecord(rootView) && !forceTable ? 'Record' : 'Table' })] }), sourceContent && _jsxs(ViewTab, { id: "jsonView-json-tab-source", value: "source", path: "Source", children: [_jsx(Braces, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "sr-only", children: "Source" })] })] }, "view-tabs"))] }))] })), _jsx(TabsContent, { value: selectedTab, className: cn('mt-2 min-w-0', fillHeight && 'flex min-h-0 flex-1 flex-col'), children: selectedTab === 'source' ? sourceContent : _jsx(ConfiguredView, { forceTable: forceTable, compiled: compiled, copyFileName: defaultKey === 'root' ? 'document.json' : defaultKey, editing: editing, filePath: filePath, fillHeight: fillHeight, onCurrentPathChange: rememberCurrentPath, onSaveView: saveColumnView, query: query, rawView: columnRawView, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, uiSize: uiSize, view: renderedView }, selectedTab) })] }) }));
322
+ }
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { UiSize } from '../lib/ui-size.js';
3
+ import { type CompiledJsonViewMetadata, type CompiledJsonViewView } from '@script-it/json-views-core';
4
+ import { type JsonViewSchemaExternalWidgetRenderer } from './schema-value.js';
5
+ import { type JsonViewJsonEditing, type JsonViewerProps } from './view-types.js';
6
+ export declare function KanbanView({ compiled, copyFileName, editing, filePath, fillHeight, query, onCurrentPathChange, onSaveView, rawView, renderExternalWidget, renderMarkdown, uiSize, view, }: {
7
+ compiled: CompiledJsonViewMetadata;
8
+ copyFileName: string;
9
+ editing?: JsonViewJsonEditing;
10
+ filePath?: string;
11
+ fillHeight: boolean;
12
+ query: string;
13
+ onCurrentPathChange?: JsonViewerProps['onCurrentPathChange'];
14
+ onSaveView?: (value: Record<string, unknown>) => Promise<void>;
15
+ rawView?: Record<string, unknown>;
16
+ renderExternalWidget?: JsonViewSchemaExternalWidgetRenderer;
17
+ renderMarkdown?: (content: string) => ReactNode;
18
+ uiSize?: UiSize;
19
+ view: CompiledJsonViewView;
20
+ }): import("react").JSX.Element;