@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.
- package/LICENSE +21 -0
- package/README.md +301 -0
- package/THIRD_PARTY_NOTICES.txt +4917 -0
- package/dist/browser-device.d.ts +21 -0
- package/dist/browser-device.js +22 -0
- package/dist/file-content-edit-state.d.ts +10 -0
- package/dist/file-content-edit-state.js +1 -0
- package/dist/file-content-view.d.ts +8 -0
- package/dist/file-content-view.js +7 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +8 -0
- package/dist/json/copy-json.d.ts +15 -0
- package/dist/json/copy-json.js +52 -0
- package/dist/json/html-preview.d.ts +5 -0
- package/dist/json/html-preview.js +12 -0
- package/dist/json/html-view/native-control.d.ts +44 -0
- package/dist/json/html-view/native-control.js +96 -0
- package/dist/json/html-view/settings.d.ts +2 -0
- package/dist/json/html-view/settings.js +58 -0
- package/dist/json/html-view/template.d.ts +12 -0
- package/dist/json/html-view/template.js +55 -0
- package/dist/json/html-view.d.ts +9 -0
- package/dist/json/html-view.js +163 -0
- package/dist/json/json-content.d.ts +61 -0
- package/dist/json/json-content.js +433 -0
- package/dist/json/json-source-editor.d.ts +36 -0
- package/dist/json/json-source-editor.js +203 -0
- package/dist/json/json-view.d.ts +3 -0
- package/dist/json/json-view.js +322 -0
- package/dist/json/kanban-view.d.ts +20 -0
- package/dist/json/kanban-view.js +328 -0
- package/dist/json/long-text.d.ts +3 -0
- package/dist/json/long-text.js +6 -0
- package/dist/json/record-view.d.ts +38 -0
- package/dist/json/record-view.js +85 -0
- package/dist/json/row-mutations.d.ts +7 -0
- package/dist/json/row-mutations.js +93 -0
- package/dist/json/schema-value.d.ts +57 -0
- package/dist/json/schema-value.js +294 -0
- package/dist/json/source-default.d.ts +1 -0
- package/dist/json/source-default.js +1 -0
- package/dist/json/source-edit-capabilities.d.ts +8 -0
- package/dist/json/source-edit-capabilities.js +22 -0
- package/dist/json/source-literals.d.ts +15 -0
- package/dist/json/source-literals.js +24 -0
- package/dist/json/source-recovery.d.ts +32 -0
- package/dist/json/source-recovery.js +38 -0
- package/dist/json/source-selection.d.ts +3 -0
- package/dist/json/source-selection.js +16 -0
- package/dist/json/table-view.d.ts +20 -0
- package/dist/json/table-view.js +300 -0
- package/dist/json/use-kanban-card-drag.d.ts +13 -0
- package/dist/json/use-kanban-card-drag.js +255 -0
- package/dist/json/use-kanban-column-drag.d.ts +14 -0
- package/dist/json/use-kanban-column-drag.js +179 -0
- package/dist/json/view-diagnostics.d.ts +4 -0
- package/dist/json/view-diagnostics.js +26 -0
- package/dist/json/view-model.d.ts +27 -0
- package/dist/json/view-model.js +180 -0
- package/dist/json/view-save-status.d.ts +12 -0
- package/dist/json/view-save-status.js +38 -0
- package/dist/json/view-settings.d.ts +36 -0
- package/dist/json/view-settings.js +661 -0
- package/dist/json/view-types.d.ts +47 -0
- package/dist/json/view-types.js +4 -0
- package/dist/json/viewers/general/general-json-view.d.ts +46 -0
- package/dist/json/viewers/general/general-json-view.js +163 -0
- package/dist/json/viewers/general/json-flatten.d.ts +33 -0
- package/dist/json/viewers/general/json-flatten.js +51 -0
- package/dist/json/viewers/general/json-node-view.d.ts +43 -0
- package/dist/json/viewers/general/json-node-view.js +215 -0
- package/dist/lib/clipboard.d.ts +1 -0
- package/dist/lib/clipboard.js +24 -0
- package/dist/lib/cn.d.ts +2 -0
- package/dist/lib/cn.js +5 -0
- package/dist/lib/file-data.d.ts +7 -0
- package/dist/lib/file-data.js +5 -0
- package/dist/lib/floating-portal-selectors.d.ts +4 -0
- package/dist/lib/floating-portal-selectors.js +4 -0
- package/dist/lib/time.d.ts +24 -0
- package/dist/lib/time.js +60 -0
- package/dist/lib/ui-size.d.ts +28 -0
- package/dist/lib/ui-size.js +39 -0
- package/dist/lib/use-anchored-position.d.ts +5 -0
- package/dist/lib/use-anchored-position.js +65 -0
- package/dist/lib/use-dismiss.d.ts +31 -0
- package/dist/lib/use-dismiss.js +65 -0
- package/dist/primitives/boolean-toggle.d.ts +10 -0
- package/dist/primitives/boolean-toggle.js +11 -0
- package/dist/primitives/button.d.ts +11 -0
- package/dist/primitives/button.js +34 -0
- package/dist/primitives/checkbox.d.ts +4 -0
- package/dist/primitives/checkbox.js +8 -0
- package/dist/primitives/choice-control.d.ts +29 -0
- package/dist/primitives/choice-control.js +131 -0
- package/dist/primitives/dialog.d.ts +29 -0
- package/dist/primitives/dialog.js +25 -0
- package/dist/primitives/dropdown-menu.d.ts +37 -0
- package/dist/primitives/dropdown-menu.js +39 -0
- package/dist/primitives/inline-feedback-action.d.ts +13 -0
- package/dist/primitives/inline-feedback-action.js +6 -0
- package/dist/primitives/menu-select.d.ts +12 -0
- package/dist/primitives/menu-select.js +7 -0
- package/dist/primitives/multi-select.d.ts +18 -0
- package/dist/primitives/multi-select.js +7 -0
- package/dist/primitives/number-input.d.ts +10 -0
- package/dist/primitives/number-input.js +8 -0
- package/dist/primitives/option-pill.d.ts +13 -0
- package/dist/primitives/option-pill.js +50 -0
- package/dist/primitives/tabs.d.ts +7 -0
- package/dist/primitives/tabs.js +12 -0
- package/dist/primitives/textarea.d.ts +4 -0
- package/dist/primitives/textarea.js +8 -0
- package/dist/primitives/tooltip.d.ts +31 -0
- package/dist/primitives/tooltip.js +37 -0
- package/dist/standalone.css +113 -0
- package/dist/structured-data/atomic-value-editor.d.ts +33 -0
- package/dist/structured-data/atomic-value-editor.js +195 -0
- package/dist/structured-data/atomic-value.d.ts +3 -0
- package/dist/structured-data/atomic-value.js +6 -0
- package/dist/structured-data/column-controls.d.ts +21 -0
- package/dist/structured-data/column-controls.js +180 -0
- package/dist/structured-data/copy-json-action.d.ts +10 -0
- package/dist/structured-data/copy-json-action.js +87 -0
- package/dist/structured-data/delete-row-action.d.ts +10 -0
- package/dist/structured-data/delete-row-action.js +37 -0
- package/dist/structured-data/edit-base.d.ts +3 -0
- package/dist/structured-data/edit-base.js +8 -0
- package/dist/structured-data/filter-value.d.ts +2 -0
- package/dist/structured-data/filter-value.js +28 -0
- package/dist/structured-data/row-selection-icon.d.ts +9 -0
- package/dist/structured-data/row-selection-icon.js +6 -0
- package/dist/structured-data/structured-cell-fill-context.d.ts +6 -0
- package/dist/structured-data/structured-cell-fill-context.js +7 -0
- package/dist/structured-data/structured-data-content.d.ts +1 -0
- package/dist/structured-data/structured-data-content.js +1 -0
- package/dist/structured-data/table-types.d.ts +108 -0
- package/dist/structured-data/table-types.js +1 -0
- package/dist/structured-data/use-metadata-persistence.d.ts +15 -0
- package/dist/structured-data/use-metadata-persistence.js +18 -0
- package/dist/structured-data/use-optimistic-text-document.d.ts +22 -0
- package/dist/structured-data/use-optimistic-text-document.js +30 -0
- package/dist/structured-data/value-cell.d.ts +15 -0
- package/dist/structured-data/value-cell.js +81 -0
- package/dist/styles.css +2 -0
- package/dist/surface.d.ts +14 -0
- package/dist/surface.js +73 -0
- package/dist/tabular-data-view.d.ts +5 -0
- package/dist/tabular-data-view.js +493 -0
- package/dist/viewer-state.d.ts +72 -0
- package/dist/viewer-state.js +79 -0
- package/dist/widget-registry.d.ts +60 -0
- package/dist/widget-registry.js +126 -0
- package/dist/widgets/boolean-widget.d.ts +2 -0
- package/dist/widgets/boolean-widget.js +15 -0
- package/dist/widgets/date-picker-widget.d.ts +18 -0
- package/dist/widgets/date-picker-widget.js +265 -0
- package/dist/widgets/dropdown-widget.d.ts +10 -0
- package/dist/widgets/dropdown-widget.js +19 -0
- package/dist/widgets/multi-select-widget.d.ts +11 -0
- package/dist/widgets/multi-select-widget.js +46 -0
- package/dist/widgets/number-widget.d.ts +2 -0
- package/dist/widgets/number-widget.js +19 -0
- package/dist/widgets/read-only-field.d.ts +36 -0
- package/dist/widgets/read-only-field.js +39 -0
- package/dist/widgets/text-widget.d.ts +2 -0
- package/dist/widgets/text-widget.js +19 -0
- package/dist/widgets/textarea-widget.d.ts +2 -0
- package/dist/widgets/textarea-widget.js +34 -0
- package/dist/widgets/types.d.ts +53 -0
- package/dist/widgets/types.js +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Format-neutral structured document orchestration with JSON-compatible wrappers. */
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { compileJsonViewMetadata, projectJsonViewCollection, convertToJsonViewsDocument, rebaseJsonViewsMetadata, csvFormat, formatJsonSource, getValueAtPath, inspectJsonSource, jsonFormatForSource, jsonObjectFormat, setJsonValueAtPath, schemaForJsonViewPath, jsonSourceRangesAtPaths, removeJsonValuesInSource, upsertJsonObjectPropertyInSource, upsertJsonObjectPropertiesInSource, } from '@script-it/json-views-core';
|
|
5
|
+
import { JsonViewsSurface } from '../surface.js';
|
|
6
|
+
import { useOptimisticTextDocument } from '../structured-data/use-optimistic-text-document.js';
|
|
7
|
+
import { useMetadataPersistence } from '../structured-data/use-metadata-persistence.js';
|
|
8
|
+
import { JsonViewer } from './json-view.js';
|
|
9
|
+
import { useJsonViewsRegistries } from '../widget-registry.js';
|
|
10
|
+
import { assertSourceReplacementSafe, assertUnambiguousSourcePath, replacementIssue } from './source-edit-capabilities.js';
|
|
11
|
+
import { JsonSourceLiteralsProvider } from './source-literals.js';
|
|
12
|
+
import { JsonRendererBoundary, JsonSourceRecovery } from './source-recovery.js';
|
|
13
|
+
import { TooltipProvider } from '../primitives/tooltip.js';
|
|
14
|
+
import { ViewSaveStatus } from './view-save-status.js';
|
|
15
|
+
import { SourceEditor, SourceFormatButton } from './json-source-editor.js';
|
|
16
|
+
import { EditBaseContext } from '../structured-data/edit-base.js';
|
|
17
|
+
import { prefersSource } from './source-default.js';
|
|
18
|
+
import { coversAllDocumentData } from './source-selection.js';
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
function withOptionColor(metadata, schemaPath, optionValue, color) {
|
|
23
|
+
const schema = isRecord(metadata.schema) ? metadata.schema : {};
|
|
24
|
+
const descriptor = schema[schemaPath];
|
|
25
|
+
if (!isRecord(descriptor) || (descriptor.type !== 'select' && descriptor.type !== 'multi-select')) {
|
|
26
|
+
throw new TypeError('Only select and multi-select annotations can have option colors');
|
|
27
|
+
}
|
|
28
|
+
const optionColors = isRecord(descriptor.optionColors) ? descriptor.optionColors : {};
|
|
29
|
+
return {
|
|
30
|
+
...metadata, version: 1,
|
|
31
|
+
schema: { ...schema, [schemaPath]: { ...descriptor, optionColors: { ...optionColors, [optionValue]: color } } },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function filenameForConversion(path) {
|
|
35
|
+
const filename = path?.split('/').filter(Boolean).pop() || 'document';
|
|
36
|
+
return `${filename.replace(/\.(?:csv|json)$/i, '') || 'document'}.json`;
|
|
37
|
+
}
|
|
38
|
+
function clonedRootWith(root, replacements) {
|
|
39
|
+
const clone = structuredClone(root);
|
|
40
|
+
if (clone === null || typeof clone !== 'object')
|
|
41
|
+
throw new TypeError('The document root cannot be edited structurally');
|
|
42
|
+
replacements.forEach(({ path, value }) => setJsonValueAtPath(clone, path, value));
|
|
43
|
+
return clone;
|
|
44
|
+
}
|
|
45
|
+
export function StructuredDataContent(props) {
|
|
46
|
+
return _jsx(JsonViewsSurface, { theme: props.theme, portalContainer: props.portalContainer, presentationState: props.presentationState, onPresentationStateChange: props.onPresentationStateChange, children: _jsx(StructuredDataContentSession, { ...props }, `${props.documentId ?? props.path ?? 'document'}:${props.adapter.format}`) }, props.documentId ?? props.path ?? 'document');
|
|
47
|
+
}
|
|
48
|
+
export function JSONContent(props) {
|
|
49
|
+
let adapter = jsonObjectFormat;
|
|
50
|
+
// The editable draft is the current document while the source editor is open.
|
|
51
|
+
// Hosts may keep `content` at the last persisted revision until a save finishes,
|
|
52
|
+
// so choosing the adapter from it would incorrectly reject a valid root-shape
|
|
53
|
+
// change (for example, an object replaced with an array).
|
|
54
|
+
try {
|
|
55
|
+
adapter = jsonFormatForSource(props.edit?.isEditing ? props.edit.editContent : props.content);
|
|
56
|
+
}
|
|
57
|
+
catch { /* Recovery handles invalid JSON. */ }
|
|
58
|
+
return _jsx(StructuredDataContent, { ...props, adapter: adapter });
|
|
59
|
+
}
|
|
60
|
+
export function CSVContent(props) {
|
|
61
|
+
return _jsx(StructuredDataContent, { ...props, adapter: csvFormat });
|
|
62
|
+
}
|
|
63
|
+
function StructuredDataContentSession({ content, adapter, documentId, revision, metadata, metadataPersistence, presentationState, onMetadataChange, onRequestMetadataPersistence, metadataConversionConfirmation = 'viewer', allowRootArrayWrapping = true, path, uiSize, fillHeight = false, edit, onSave, isSaving, isDirty, saveError, sourceVisible, onSourceVisibleChange, clearBehavior = 'null', onDraftRowsChange, renderExternalWidget, renderMarkdown, }) {
|
|
64
|
+
const { types } = useJsonViewsRegistries();
|
|
65
|
+
const [editorGeneration, setEditorGeneration] = useState(0);
|
|
66
|
+
const [draftRows, setDraftRows] = useState([]);
|
|
67
|
+
const [localSourceVisible, setLocalSourceVisible] = useState(() => presentationState?.activeView ? presentationState.activeView === 'source' : adapter.format !== 'csv' && prefersSource(content, metadata));
|
|
68
|
+
const [currentViewPaths, setCurrentViewPaths] = useState([[]]);
|
|
69
|
+
const rememberSourcePaths = useCallback((path, paths) => {
|
|
70
|
+
setCurrentViewPaths(paths ?? [path]);
|
|
71
|
+
}, []);
|
|
72
|
+
const document = useOptimisticTextDocument({
|
|
73
|
+
content, documentKey: documentId ?? path, revision,
|
|
74
|
+
saveContent: onSave ?? edit?.onCommitContent, saving: isSaving ?? edit?.isSaving,
|
|
75
|
+
});
|
|
76
|
+
const showingSource = sourceVisible ?? (edit?.isEditing === true || localSourceVisible);
|
|
77
|
+
const setShowingSource = useCallback((visible) => {
|
|
78
|
+
if (sourceVisible === undefined)
|
|
79
|
+
setLocalSourceVisible(visible);
|
|
80
|
+
onSourceVisibleChange?.(visible);
|
|
81
|
+
}, [onSourceVisibleChange, sourceVisible]);
|
|
82
|
+
const editSource = useCallback((nextContent) => {
|
|
83
|
+
document.edit(nextContent);
|
|
84
|
+
edit?.onEditChange(nextContent);
|
|
85
|
+
}, [document.edit, edit?.onEditChange]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (sourceVisible === undefined && edit?.isEditing && edit.editContent !== document.content) {
|
|
88
|
+
document.edit(edit.editContent);
|
|
89
|
+
}
|
|
90
|
+
}, [document.content, document.edit, edit?.editContent, edit?.isEditing, sourceVisible]);
|
|
91
|
+
const parsed = useMemo(() => {
|
|
92
|
+
try {
|
|
93
|
+
return { ...adapter.inspect(document.content), error: null };
|
|
94
|
+
}
|
|
95
|
+
catch (cause) {
|
|
96
|
+
return { root: undefined, state: undefined, diagnostics: [], error: cause instanceof Error ? cause.message : `Invalid ${adapter.format}` };
|
|
97
|
+
}
|
|
98
|
+
}, [adapter, document.content]);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (parsed.error && !showingSource)
|
|
101
|
+
setShowingSource(true);
|
|
102
|
+
}, [parsed.error, showingSource, setShowingSource]);
|
|
103
|
+
const embedded = adapter.format !== 'csv' && isRecord(parsed.root) && Object.prototype.hasOwnProperty.call(parsed.root, '$jsonviews');
|
|
104
|
+
const { persistence, effectiveMetadata, saveSessionMetadata } = useMetadataPersistence({
|
|
105
|
+
metadata: embedded ? undefined : metadata,
|
|
106
|
+
mode: embedded ? 'embedded' : metadataPersistence ?? 'session', format: adapter.format, onMetadataChange,
|
|
107
|
+
});
|
|
108
|
+
const compilation = useMemo(() => {
|
|
109
|
+
try {
|
|
110
|
+
const options = { ...(effectiveMetadata !== undefined ? { metadata: effectiveMetadata } : {}), ...(draftRows.length > 0 ? { suppressRequiredPaths: draftRows } : {}) };
|
|
111
|
+
return { compiled: compileJsonViewMetadata(parsed.root, types, options), error: undefined };
|
|
112
|
+
}
|
|
113
|
+
catch (cause) {
|
|
114
|
+
return { compiled: undefined, error: cause instanceof Error ? cause.message : 'Could not compile annotations' };
|
|
115
|
+
}
|
|
116
|
+
}, [parsed.root, types, types.version, effectiveMetadata, draftRows]);
|
|
117
|
+
const compiled = compilation.compiled;
|
|
118
|
+
const metadataWritable = compiled?.status !== 'unsupported-version' && compiled?.status !== 'invalid'
|
|
119
|
+
&& (embedded || metadata === undefined || onMetadataChange !== undefined);
|
|
120
|
+
const formatDiagnostics = parsed.diagnostics;
|
|
121
|
+
const sourceDiagnostics = useMemo(() => {
|
|
122
|
+
if (adapter.format === 'csv')
|
|
123
|
+
return [];
|
|
124
|
+
try {
|
|
125
|
+
return inspectJsonSource(document.content).diagnostics;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}, [adapter.format, document.content]);
|
|
131
|
+
const structuredEditingAllowed = !formatDiagnostics.some((item) => item.severity === 'error');
|
|
132
|
+
const commitSource = useCallback((source) => document.commit(() => source), [document.commit]);
|
|
133
|
+
const sourcePersistenceOwnedByHost = sourceVisible !== undefined || edit?.isEditing === true;
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (sourcePersistenceOwnedByHost || !document.canCommit || !document.dirty || document.saving || document.error || parsed.error)
|
|
136
|
+
return;
|
|
137
|
+
const timer = setTimeout(() => { void commitSource(document.content).catch(() => { }); }, 300);
|
|
138
|
+
return () => clearTimeout(timer);
|
|
139
|
+
}, [commitSource, document.canCommit, document.content, document.dirty, document.error, document.saving, parsed.error, sourcePersistenceOwnedByHost]);
|
|
140
|
+
const currentMetadata = useCallback(() => {
|
|
141
|
+
if (isRecord(effectiveMetadata))
|
|
142
|
+
return effectiveMetadata;
|
|
143
|
+
if (isRecord(compiled?.metadata))
|
|
144
|
+
return compiled.metadata;
|
|
145
|
+
return { version: 1, schema: {}, views: [] };
|
|
146
|
+
}, [compiled?.metadata, effectiveMetadata]);
|
|
147
|
+
const requestConversion = useCallback(async (reason, nextMetadata, root = parsed.root) => {
|
|
148
|
+
if (adapter.format !== 'json-array' && adapter.format !== 'csv')
|
|
149
|
+
throw new TypeError('This document does not require conversion');
|
|
150
|
+
if (!onRequestMetadataPersistence)
|
|
151
|
+
throw new Error('Saving annotations requires converting this document to object-root JSON');
|
|
152
|
+
const accepted = await onRequestMetadataPersistence({
|
|
153
|
+
reason, sourceFormat: adapter.format, proposedFilename: filenameForConversion(path),
|
|
154
|
+
convertedSource: convertToJsonViewsDocument({
|
|
155
|
+
root, metadata: nextMetadata, sourceFormat: adapter.format,
|
|
156
|
+
...(root === parsed.root && adapter.format === 'json-array' ? { source: document.content } : {}),
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
if (accepted === false)
|
|
160
|
+
throw new Error('Conversion cancelled');
|
|
161
|
+
}, [adapter.format, document.content, onRequestMetadataPersistence, parsed.root, path]);
|
|
162
|
+
const isDraftRow = useCallback((valuePath) => (draftRows.some((candidate) => candidate.length <= valuePath.length && candidate.every((part, index) => part === valuePath[index]))), [draftRows]);
|
|
163
|
+
const dismissDraftForPath = useCallback((valuePath) => {
|
|
164
|
+
setDraftRows((current) => current.filter((candidate) => !(candidate.length <= valuePath.length && candidate.every((part, index) => part === valuePath[index]))));
|
|
165
|
+
}, []);
|
|
166
|
+
const replaceValues = useCallback(async (replacements) => {
|
|
167
|
+
if (adapter.format === 'csv' && replacements.some((replacement) => !adapter.canRepresent(replacement.value).representable)) {
|
|
168
|
+
await requestConversion('unsupported-csv-value', currentMetadata(), clonedRootWith(parsed.root, replacements));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
await document.commit((source) => {
|
|
172
|
+
if (adapter.format !== 'csv')
|
|
173
|
+
assertSourceReplacementSafe(source, replacements.map((replacement) => replacement.path));
|
|
174
|
+
for (const replacement of replacements) {
|
|
175
|
+
const descriptor = compiled && schemaForJsonViewPath(compiled.schema, replacement.path)?.descriptor;
|
|
176
|
+
const issue = descriptor && types.validate(replacement.value, descriptor);
|
|
177
|
+
if (issue)
|
|
178
|
+
throw new Error(issue);
|
|
179
|
+
}
|
|
180
|
+
return adapter.replaceMany(source, adapter.inspect(source).state, replacements);
|
|
181
|
+
});
|
|
182
|
+
replacements.forEach((replacement) => dismissDraftForPath(replacement.path));
|
|
183
|
+
}, [adapter, compiled, currentMetadata, dismissDraftForPath, document, parsed.root, requestConversion, types]);
|
|
184
|
+
const replaceValue = useCallback((valuePath, value) => replaceValues([{ path: valuePath, value }]), [replaceValues]);
|
|
185
|
+
const removeValues = useCallback(async (valuePaths) => {
|
|
186
|
+
await document.commit((source) => {
|
|
187
|
+
if (adapter.format !== 'csv')
|
|
188
|
+
assertUnambiguousSourcePath(source, valuePaths);
|
|
189
|
+
return adapter.removeMany(source, adapter.inspect(source).state, valuePaths);
|
|
190
|
+
});
|
|
191
|
+
valuePaths.forEach(dismissDraftForPath);
|
|
192
|
+
}, [adapter, dismissDraftForPath, document]);
|
|
193
|
+
const clearValue = useCallback((valuePath) => (clearBehavior === 'remove' ? removeValues([valuePath]) : replaceValue(valuePath, adapter.format === 'csv' ? '' : null)), [adapter.format, clearBehavior, removeValues, replaceValue]);
|
|
194
|
+
const commitGeneratedSource = useCallback((transform) => document.commit((source) => {
|
|
195
|
+
const next = transform(source);
|
|
196
|
+
return adapter.format === 'csv' ? next : formatJsonSource(next);
|
|
197
|
+
}), [adapter.format, document.commit]);
|
|
198
|
+
const appendArrayItem = useCallback(async (arrayPath, value) => {
|
|
199
|
+
let appendValue = value;
|
|
200
|
+
if (adapter.format === 'csv' && isRecord(value)) {
|
|
201
|
+
// Inferred date/select columns use null for a new blank JSON row; CSV's
|
|
202
|
+
// native blank is an empty cell, so keep ordinary row creation native.
|
|
203
|
+
const csvRow = Object.fromEntries(Object.entries(value).map(([key, item]) => [key, item === null ? '' : item]));
|
|
204
|
+
appendValue = csvRow;
|
|
205
|
+
if (Object.values(csvRow).some((item) => !adapter.canRepresent(item).representable)) {
|
|
206
|
+
await requestConversion('unsupported-csv-value', currentMetadata(), Array.isArray(parsed.root) ? [...parsed.root, value] : parsed.root);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
await commitGeneratedSource((source) => {
|
|
211
|
+
if (adapter.format !== 'csv')
|
|
212
|
+
assertUnambiguousSourcePath(source, [arrayPath]);
|
|
213
|
+
return adapter.append(source, adapter.inspect(source).state, arrayPath, appendValue);
|
|
214
|
+
});
|
|
215
|
+
}, [adapter, currentMetadata, commitGeneratedSource, parsed.root, requestConversion]);
|
|
216
|
+
const registerDraftRow = useCallback((rowPath) => {
|
|
217
|
+
setDraftRows((current) => current.some((candidate) => candidate.length === rowPath.length && candidate.every((part, index) => part === rowPath[index])) ? current : [...current, rowPath]);
|
|
218
|
+
}, []);
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
onDraftRowsChange?.(draftRows);
|
|
221
|
+
return () => { onDraftRowsChange?.([]); };
|
|
222
|
+
}, [draftRows, onDraftRowsChange]);
|
|
223
|
+
const addColumn = useCallback(async (arrayPath, property, schemaPath, descriptor, viewDeclarationIndex) => {
|
|
224
|
+
if (!metadataWritable)
|
|
225
|
+
throw new Error('These annotations cannot be edited');
|
|
226
|
+
if (adapter.format !== 'json-object' || persistence !== 'embedded')
|
|
227
|
+
throw new Error('Columns can only be added to object-root JSON documents');
|
|
228
|
+
await commitGeneratedSource((source) => {
|
|
229
|
+
assertUnambiguousSourcePath(source, [arrayPath]);
|
|
230
|
+
assertSourceReplacementSafe(source, [['$jsonviews']]);
|
|
231
|
+
const root = JSON.parse(source);
|
|
232
|
+
const rows = getValueAtPath(root, arrayPath);
|
|
233
|
+
if (!Array.isArray(rows) || rows.some((row) => !isRecord(row)))
|
|
234
|
+
throw new TypeError('Columns can only be added to an array of objects');
|
|
235
|
+
if (rows.some((row) => Object.prototype.hasOwnProperty.call(row, property)))
|
|
236
|
+
throw new Error(`A property named ${property} already exists`);
|
|
237
|
+
const initialValue = descriptor.type === 'checkbox' ? false
|
|
238
|
+
: descriptor.type === 'number' ? 0
|
|
239
|
+
: descriptor.type === 'multi-select' ? []
|
|
240
|
+
: ['text', 'markdown', 'html'].includes(String(descriptor.type)) ? ''
|
|
241
|
+
: null;
|
|
242
|
+
const nextSource = upsertJsonObjectPropertiesInSource(source, rows.map((_, index) => ({
|
|
243
|
+
objectPath: [...arrayPath, index], key: property, value: initialValue,
|
|
244
|
+
})));
|
|
245
|
+
const nextRoot = JSON.parse(nextSource);
|
|
246
|
+
if (!isRecord(nextRoot))
|
|
247
|
+
throw new TypeError('JSON metadata requires an object root');
|
|
248
|
+
const base = isRecord(nextRoot.$jsonviews) ? nextRoot.$jsonviews : currentMetadata();
|
|
249
|
+
const schema = isRecord(base.schema) ? base.schema : {};
|
|
250
|
+
const views = Array.isArray(base.views) ? [...base.views] : [];
|
|
251
|
+
const rawView = views[viewDeclarationIndex];
|
|
252
|
+
if (!isRecord(rawView))
|
|
253
|
+
throw new Error('This table view cannot be updated');
|
|
254
|
+
const existingView = compileJsonViewMetadata(root).views.find((view) => view.declarationIndex === viewDeclarationIndex);
|
|
255
|
+
const columns = Array.isArray(rawView.columns) && rawView.columns.length > 0 ? [...rawView.columns]
|
|
256
|
+
: existingView ? projectJsonViewCollection(root, { ...existingView, filter: undefined, sort: [] }).columns.map((column) => ({ label: column.label, path: column.path.source })) : [];
|
|
257
|
+
views[viewDeclarationIndex] = { ...rawView, columns: [...columns, { label: typeof descriptor.title === 'string' ? descriptor.title : property, path: schemaPath }] };
|
|
258
|
+
return upsertJsonObjectPropertyInSource(nextSource, [], '$jsonviews', {
|
|
259
|
+
...base,
|
|
260
|
+
version: 1,
|
|
261
|
+
schema: { ...schema, [schemaPath]: descriptor },
|
|
262
|
+
views,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}, [adapter.format, currentMetadata, commitGeneratedSource, metadataWritable, persistence]);
|
|
266
|
+
const deleteColumn = useCallback(async (arrayPath, property, schemaPath) => {
|
|
267
|
+
if (!metadataWritable)
|
|
268
|
+
throw new Error('These annotations cannot be edited');
|
|
269
|
+
if (adapter.format !== 'json-object' || persistence !== 'embedded')
|
|
270
|
+
throw new Error('Columns can only be deleted from object-root JSON documents');
|
|
271
|
+
await commitGeneratedSource((source) => {
|
|
272
|
+
const root = JSON.parse(source);
|
|
273
|
+
const rows = getValueAtPath(root, arrayPath);
|
|
274
|
+
if (!Array.isArray(rows) || rows.some((row) => !isRecord(row)))
|
|
275
|
+
throw new TypeError('Columns can only be deleted from an array of objects');
|
|
276
|
+
const paths = rows.flatMap((row, index) => Object.prototype.hasOwnProperty.call(row, property) ? [[...arrayPath, index, property]] : []);
|
|
277
|
+
assertUnambiguousSourcePath(source, paths);
|
|
278
|
+
assertSourceReplacementSafe(source, [['$jsonviews']]);
|
|
279
|
+
let nextSource = removeJsonValuesInSource(source, paths);
|
|
280
|
+
const nextRoot = JSON.parse(nextSource);
|
|
281
|
+
if (!isRecord(nextRoot))
|
|
282
|
+
throw new TypeError('JSON metadata requires an object root');
|
|
283
|
+
const base = isRecord(nextRoot.$jsonviews) ? nextRoot.$jsonviews : currentMetadata();
|
|
284
|
+
const schema = isRecord(base.schema) ? { ...base.schema } : {};
|
|
285
|
+
delete schema[schemaPath];
|
|
286
|
+
const views = Array.isArray(base.views) ? base.views.map((value) => {
|
|
287
|
+
if (!isRecord(value))
|
|
288
|
+
return value;
|
|
289
|
+
const next = { ...value };
|
|
290
|
+
if (Array.isArray(next.columns))
|
|
291
|
+
next.columns = next.columns.filter((column) => !isRecord(column) || column.path !== schemaPath);
|
|
292
|
+
if (Array.isArray(next.sort))
|
|
293
|
+
next.sort = next.sort.filter((sort) => !isRecord(sort) || sort.path !== schemaPath);
|
|
294
|
+
if (isRecord(next.filter) && Array.isArray(next.filter.rules)) {
|
|
295
|
+
const rules = next.filter.rules.filter((rule) => !isRecord(rule) || rule.path !== schemaPath);
|
|
296
|
+
if (rules.length > 0)
|
|
297
|
+
next.filter = { ...next.filter, rules };
|
|
298
|
+
else
|
|
299
|
+
delete next.filter;
|
|
300
|
+
}
|
|
301
|
+
for (const key of ['title', 'groupBy', 'orderPath']) {
|
|
302
|
+
if (next[key] !== schemaPath)
|
|
303
|
+
continue;
|
|
304
|
+
delete next[key];
|
|
305
|
+
if (key === 'groupBy')
|
|
306
|
+
delete next.groupOrder;
|
|
307
|
+
}
|
|
308
|
+
return next;
|
|
309
|
+
}) : [];
|
|
310
|
+
nextSource = upsertJsonObjectPropertyInSource(nextSource, [], '$jsonviews', { ...base, version: 1, schema, views });
|
|
311
|
+
return nextSource;
|
|
312
|
+
});
|
|
313
|
+
}, [adapter.format, currentMetadata, commitGeneratedSource, metadataWritable, persistence]);
|
|
314
|
+
const setOptionColor = useCallback(async (schemaPath, optionValue, color) => {
|
|
315
|
+
if (!metadataWritable || !compiled)
|
|
316
|
+
throw new Error('These annotations cannot be edited');
|
|
317
|
+
const next = withOptionColor(currentMetadata(), schemaPath, optionValue, color);
|
|
318
|
+
const candidate = compileJsonViewMetadata(parsed.root, types, { metadata: next });
|
|
319
|
+
if (!candidate.schema.some((entry) => entry.declaration === schemaPath)) {
|
|
320
|
+
throw new Error(candidate.diagnostics.find((item) => item.scope === 'schema')?.message ?? 'Invalid option color');
|
|
321
|
+
}
|
|
322
|
+
if (persistence === 'session') {
|
|
323
|
+
await saveSessionMetadata(next);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (adapter.format !== 'json-object' || persistence !== 'embedded') {
|
|
327
|
+
await requestConversion('save-option-colors', next);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
await commitGeneratedSource((source) => {
|
|
331
|
+
const root = JSON.parse(source);
|
|
332
|
+
if (!isRecord(root))
|
|
333
|
+
throw new TypeError('JSON metadata requires an object root');
|
|
334
|
+
assertSourceReplacementSafe(source, [['$jsonviews']]);
|
|
335
|
+
return upsertJsonObjectPropertyInSource(source, [], '$jsonviews', next);
|
|
336
|
+
});
|
|
337
|
+
}, [adapter.format, compiled, currentMetadata, commitGeneratedSource, metadataWritable, parsed.root, persistence, requestConversion, saveSessionMetadata, types]);
|
|
338
|
+
const replaceViews = useCallback(async (views) => {
|
|
339
|
+
if (!metadataWritable || !compiled)
|
|
340
|
+
throw new Error('These annotations cannot be edited');
|
|
341
|
+
const base = currentMetadata();
|
|
342
|
+
const next = {
|
|
343
|
+
...base, version: 1,
|
|
344
|
+
...(base.schema === undefined && compiled.inference && Object.keys(compiled.inference.schema).length > 0 ? { schema: compiled.inference.schema } : {}),
|
|
345
|
+
views,
|
|
346
|
+
};
|
|
347
|
+
const candidate = compileJsonViewMetadata(parsed.root, types, { metadata: next });
|
|
348
|
+
if (candidate.views.length !== views.length)
|
|
349
|
+
throw new Error(candidate.diagnostics.find((item) => item.scope === 'view')?.message ?? 'Invalid view');
|
|
350
|
+
if (persistence === 'session') {
|
|
351
|
+
await saveSessionMetadata(next);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (adapter.format !== 'json-object' || persistence !== 'embedded') {
|
|
355
|
+
await requestConversion('save-view', next);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
await commitGeneratedSource((source) => {
|
|
359
|
+
const root = JSON.parse(source);
|
|
360
|
+
if (!isRecord(root))
|
|
361
|
+
throw new TypeError('JSON metadata requires an object root');
|
|
362
|
+
assertSourceReplacementSafe(source, [['$jsonviews']]);
|
|
363
|
+
return upsertJsonObjectPropertyInSource(source, [], '$jsonviews', next);
|
|
364
|
+
});
|
|
365
|
+
}, [adapter.format, compiled, currentMetadata, commitGeneratedSource, metadataWritable, parsed.root, persistence, requestConversion, saveSessionMetadata, types]);
|
|
366
|
+
const saveViewsToSource = async () => {
|
|
367
|
+
const next = currentMetadata();
|
|
368
|
+
if (adapter.format === 'json-object' && isRecord(parsed.root)) {
|
|
369
|
+
await document.commit((source) => {
|
|
370
|
+
assertSourceReplacementSafe(source, [['$jsonviews']]);
|
|
371
|
+
return upsertJsonObjectPropertyInSource(source, [], '$jsonviews', next);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
else if (onRequestMetadataPersistence) {
|
|
375
|
+
const convertedSource = adapter.format === 'csv' || Array.isArray(parsed.root)
|
|
376
|
+
? convertToJsonViewsDocument({ root: parsed.root, metadata: next, sourceFormat: adapter.format === 'csv' ? 'csv' : 'json-array', ...(adapter.format === 'csv' ? {} : { source: document.content }) })
|
|
377
|
+
: `{\n "$jsonviews": ${JSON.stringify(rebaseJsonViewsMetadata(next), null, 2)},\n "data": ${document.content.trim()}\n}\n`;
|
|
378
|
+
await onRequestMetadataPersistence({ reason: 'save-view', sourceFormat: adapter.format === 'csv' ? 'csv' : Array.isArray(parsed.root) ? 'json-array' : 'json-scalar', proposedFilename: filenameForConversion(path), convertedSource });
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
const saveStatus = _jsx(ViewSaveStatus, { embedded: embedded, objectRoot: adapter.format === 'json-object' && isRecord(parsed.root), conversionConfirmation: metadataConversionConfirmation, saving: document.saving, dirty: isDirty || document.dirty, invalidSourceError: parsed.error ?? undefined, invalidFormat: parsed.error ? adapter.format === 'csv' ? 'CSV' : 'JSON' : undefined, error: document.error || saveError || (!metadataWritable ? 'View settings need repair before saving.' : undefined), onSave: document.canCommit && metadataWritable && (isRecord(parsed.root) && adapter.format === 'json-object' || onRequestMetadataPersistence) ? saveViewsToSource : undefined });
|
|
382
|
+
const sourceFormat = adapter.format === 'csv' ? 'CSV' : 'JSON';
|
|
383
|
+
const formattedSource = useMemo(() => {
|
|
384
|
+
if (adapter.format === 'csv')
|
|
385
|
+
return undefined;
|
|
386
|
+
try {
|
|
387
|
+
return formatJsonSource(document.content);
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
}, [adapter.format, document.content]);
|
|
393
|
+
const formatSource = !document.canCommit || formattedSource === undefined || formattedSource === document.content.trim()
|
|
394
|
+
? undefined : () => editSource(formattedSource);
|
|
395
|
+
const sourceControl = _jsxs(_Fragment, { children: [showingSource && formatSource && _jsx(SourceFormatButton, { format: sourceFormat, disabled: document.saving, onFormat: formatSource }), saveStatus] });
|
|
396
|
+
const sourceTargetRanges = useMemo(() => {
|
|
397
|
+
if (adapter.format === 'csv' || currentViewPaths.length === 0 || coversAllDocumentData(parsed.root, currentViewPaths))
|
|
398
|
+
return [];
|
|
399
|
+
try {
|
|
400
|
+
return jsonSourceRangesAtPaths(document.content, currentViewPaths);
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
}, [adapter.format, currentViewPaths, document.content, parsed.root]);
|
|
406
|
+
const sourceEditor = (_jsx(TooltipProvider, { children: _jsx(SourceEditor, { embedded: !parsed.error, saveStatus: saveStatus, content: document.content, dirty: isDirty ?? document.dirty, fillHeight: fillHeight, format: sourceFormat, invalidSourceError: parsed.error, onChange: editSource, onClose: () => setShowingSource(false), onFormat: formatSource, readOnly: !document.canCommit, saveError: document.error ?? saveError, saving: document.saving, targetRanges: sourceTargetRanges }) }));
|
|
407
|
+
if (parsed.error)
|
|
408
|
+
return sourceEditor;
|
|
409
|
+
const recoveryCommit = document.canCommit ? commitSource : undefined;
|
|
410
|
+
if (parsed.error || parsed.root === undefined || parsed.state === undefined || !compiled) {
|
|
411
|
+
return _jsx(JsonSourceRecovery, { source: document.content, error: parsed.error ?? compilation.error ?? `Could not inspect this ${adapter.format}`, onCommit: recoveryCommit });
|
|
412
|
+
}
|
|
413
|
+
const requiresMetadataConversion = persistence !== 'session' && (adapter.format === 'json-array' || adapter.format === 'csv');
|
|
414
|
+
const conversionUnavailable = requiresMetadataConversion && onRequestMetadataPersistence === undefined;
|
|
415
|
+
const viewCreationUnavailableReason = !metadataWritable
|
|
416
|
+
? 'This document’s view metadata is invalid or read-only.'
|
|
417
|
+
: conversionUnavailable
|
|
418
|
+
? `Saving a view for this ${adapter.format === 'csv' ? 'CSV file' : 'root-array JSON file'} requires a host that can create an object-root JSON copy.`
|
|
419
|
+
: persistence !== 'session' && adapter.format === 'json-array' && !allowRootArrayWrapping
|
|
420
|
+
? 'Saving views for root-array JSON is disabled by this host.'
|
|
421
|
+
: undefined;
|
|
422
|
+
const editing = document.canCommit && structuredEditingAllowed ? {
|
|
423
|
+
...(metadataWritable && persistence === 'embedded' && adapter.format === 'json-object' ? { addColumn, deleteColumn } : {}),
|
|
424
|
+
canEditViews: viewCreationUnavailableReason === undefined,
|
|
425
|
+
...(viewCreationUnavailableReason ? { viewCreationUnavailableReason } : {}),
|
|
426
|
+
viewSaveMode: requiresMetadataConversion ? 'explicit' : 'automatic',
|
|
427
|
+
canReplace: (valuePath) => adapter.format === 'csv' || replacementIssue(sourceDiagnostics, valuePath) === undefined,
|
|
428
|
+
append: appendArrayItem, registerDraftRow, isDraftRow, clear: clearValue,
|
|
429
|
+
replace: replaceValue, replaceMany: replaceValues, ...(metadataWritable ? { setOptionColor } : {}), replaceViews,
|
|
430
|
+
removeMany: removeValues, saving: document.saving,
|
|
431
|
+
} : undefined;
|
|
432
|
+
return (_jsxs(TooltipProvider, { children: [document.error && _jsxs("div", { className: "grid gap-2 px-4 py-2 text-sm", children: [_jsx("p", { role: "alert", className: "text-destructive", children: document.error }), document.dirty && _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("button", { type: "button", disabled: document.saving, className: "rounded-md border border-border px-2 py-1 disabled:opacity-50", onClick: () => { void document.commit((source) => source).catch(() => { }); }, children: "Retry save" }), _jsx("button", { type: "button", disabled: document.saving, className: "rounded-md border border-border px-2 py-1 disabled:opacity-50", onClick: () => { document.reset(); setEditorGeneration((generation) => generation + 1); }, children: "Discard unsaved changes" }), _jsxs("details", { className: "basis-full", children: [_jsx("summary", { children: "Unsaved source" }), _jsx("pre", { className: "max-h-64 overflow-auto whitespace-pre-wrap font-mono text-xs", children: document.content })] })] })] }), formatDiagnostics.length > 0 && _jsxs("details", { className: "px-4 py-2 text-sm", children: [_jsx("summary", { children: formatDiagnostics.some((item) => item.severity === 'error') ? 'This document needs repair before structured editing.' : 'Some values require source editing to preserve their exact representation.' }), _jsx(JsonSourceRecovery, { source: document.content, error: formatDiagnostics.map((item) => item.message).join(' '), onCommit: recoveryCommit })] }), _jsx(JsonRendererBoundary, { source: document.content, onCommit: recoveryCommit, children: _jsx(JsonSourceLiteralsProvider, { diagnostics: sourceDiagnostics, children: _jsx(EditBaseContext.Provider, { value: document.acknowledgedContent, children: _jsx(JsonViewer, { compiled: compiled, defaultKey: path?.split('/').pop() || 'root', filePath: documentId ?? path, sourceContent: sourceEditor, sourceVisible: showingSource, onSourceVisibleChange: setShowingSource, tabularRoot: adapter.format === 'csv', editing: editing, onCurrentPathChange: rememberSourcePaths, renderExternalWidget: renderExternalWidget, renderMarkdown: renderMarkdown, sourceControl: sourceControl, uiSize: uiSize, fillHeight: fillHeight }) }) }) }, editorGeneration)] }));
|
|
433
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { StateField } from '@codemirror/state';
|
|
3
|
+
import { type DecorationSet } from '@codemirror/view';
|
|
4
|
+
export declare function SourceToggle({ active, format, onToggle, disabled }: {
|
|
5
|
+
active: boolean;
|
|
6
|
+
format: string;
|
|
7
|
+
onToggle: () => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export interface SourceRange {
|
|
11
|
+
start: number;
|
|
12
|
+
end: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const setSourceTargetRanges: import("@codemirror/state").StateEffectType<readonly SourceRange[]>;
|
|
15
|
+
export declare const sourceTargetDecorations: StateField<DecorationSet>;
|
|
16
|
+
export declare function SourceFormatButton({ format, disabled, onFormat }: {
|
|
17
|
+
format: string;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
onFormat: () => void;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
21
|
+
export declare function SourceEditor({ embedded, content, dirty, fillHeight, format, invalidSourceError, saveStatus, onChange, onClose, onFormat, readOnly, saveError, saving, targetRanges, }: {
|
|
22
|
+
embedded?: boolean;
|
|
23
|
+
saveStatus?: ReactNode;
|
|
24
|
+
content: string;
|
|
25
|
+
dirty: boolean;
|
|
26
|
+
fillHeight: boolean;
|
|
27
|
+
format: string;
|
|
28
|
+
invalidSourceError?: string | null;
|
|
29
|
+
onChange: (content: string) => void;
|
|
30
|
+
onClose: () => void;
|
|
31
|
+
onFormat?: () => void;
|
|
32
|
+
readOnly: boolean;
|
|
33
|
+
saveError?: string;
|
|
34
|
+
saving: boolean;
|
|
35
|
+
targetRanges?: readonly SourceRange[];
|
|
36
|
+
}): import("react").JSX.Element;
|