@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,57 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { JsonViewSchemaDescriptor, ValuePath } from '@script-it/json-views-core';
|
|
3
|
+
export interface JsonViewSchemaExternalWidgetProps {
|
|
4
|
+
type: string;
|
|
5
|
+
value: unknown;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
descriptor: JsonViewSchemaDescriptor;
|
|
8
|
+
label: string;
|
|
9
|
+
onChange: (value: unknown) => void;
|
|
10
|
+
}
|
|
11
|
+
export type JsonViewSchemaExternalWidgetRenderer = (props: JsonViewSchemaExternalWidgetProps) => ReactNode;
|
|
12
|
+
export declare function withColumnOptionColors(descriptor: JsonViewSchemaDescriptor, values: readonly unknown[], offset: number): JsonViewSchemaDescriptor;
|
|
13
|
+
export declare function JsonViewSchemaValueDisplay({ compact, decorative, passive, sourcePath, descriptor, onOpen, renderMarkdown, value, }: {
|
|
14
|
+
compact?: boolean;
|
|
15
|
+
/** Suppresses duplicate checkbox semantics inside an interactive control. */
|
|
16
|
+
decorative?: boolean;
|
|
17
|
+
/** Removes nested interactions when a parent owns the cell interaction. */
|
|
18
|
+
passive?: boolean;
|
|
19
|
+
sourcePath?: ValuePath;
|
|
20
|
+
descriptor: JsonViewSchemaDescriptor;
|
|
21
|
+
onOpen: () => void;
|
|
22
|
+
renderMarkdown?: (content: string) => ReactNode;
|
|
23
|
+
value: unknown;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
25
|
+
export declare function SchemaEditor({ descriptor, disabled, label, minHeight, onCancel, onClear, onCommit, onOptionColorChange, renderExternalWidget, variant, value, }: {
|
|
26
|
+
descriptor: JsonViewSchemaDescriptor;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
label: string;
|
|
29
|
+
minHeight?: number;
|
|
30
|
+
onCancel: () => void;
|
|
31
|
+
onClear?: () => Promise<void>;
|
|
32
|
+
onCommit: (value: unknown) => Promise<void>;
|
|
33
|
+
onOptionColorChange?: (optionValue: string, color: string) => Promise<void>;
|
|
34
|
+
renderExternalWidget?: JsonViewSchemaExternalWidgetRenderer;
|
|
35
|
+
variant: 'default' | 'record-title';
|
|
36
|
+
value: unknown;
|
|
37
|
+
}): import("react").JSX.Element;
|
|
38
|
+
export declare function JsonViewSchemaValueCell({ compact, descriptor, initiallyEditing, label, onClear, onCommit, onOptionColorChange, onOpen, renderExternalWidget, renderMarkdown, saving, suppressRequiredValidation, variant, value, sourcePath, }: {
|
|
39
|
+
compact?: boolean;
|
|
40
|
+
descriptor: JsonViewSchemaDescriptor;
|
|
41
|
+
initiallyEditing?: boolean;
|
|
42
|
+
inputKey?: string;
|
|
43
|
+
label: string;
|
|
44
|
+
onClear?: () => Promise<void>;
|
|
45
|
+
onCommit?: (value: unknown) => Promise<void>;
|
|
46
|
+
onOptionColorChange?: (optionValue: string, color: string) => Promise<void>;
|
|
47
|
+
onOpen: () => void;
|
|
48
|
+
renderExternalWidget?: JsonViewSchemaExternalWidgetRenderer;
|
|
49
|
+
renderMarkdown?: (content: string) => ReactNode;
|
|
50
|
+
saving?: boolean;
|
|
51
|
+
/** Avoids showing required-field errors while a newly-created record is still untouched. */
|
|
52
|
+
suppressRequiredValidation?: boolean;
|
|
53
|
+
/** Matches a record heading's typography and occupied space while editing. */
|
|
54
|
+
variant?: 'default' | 'record-title';
|
|
55
|
+
value: unknown;
|
|
56
|
+
sourcePath?: ValuePath;
|
|
57
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { AlertTriangle, Check, ExternalLink } from 'lucide-react';
|
|
4
|
+
import { cn } from '../lib/cn.js';
|
|
5
|
+
import { OptionPill, optionColorsForValues } from '../primitives/option-pill.js';
|
|
6
|
+
import { EditValueAction, StructuredValueCellFrame } from '../structured-data/atomic-value-editor.js';
|
|
7
|
+
import { StructuredCellFillContext } from '../structured-data/structured-cell-fill-context.js';
|
|
8
|
+
import { ExpandValueButton, ValueCellContent } from '../tabular-data-view.js';
|
|
9
|
+
import { JsonSourceNumber, useJsonSourceLiterals, useJsonSourceSubtreeRisk } from './source-literals.js';
|
|
10
|
+
import { validateJsonViewSchemaValue } from '@script-it/json-views-core';
|
|
11
|
+
import { useJsonViewsRegistries } from '../widget-registry.js';
|
|
12
|
+
import { useEditBaseChanged } from '../structured-data/edit-base.js';
|
|
13
|
+
import { HtmlPreview } from './html-preview.js';
|
|
14
|
+
export function withColumnOptionColors(descriptor, values, offset) {
|
|
15
|
+
if (descriptor.type !== 'select' && descriptor.type !== 'multi-select')
|
|
16
|
+
return descriptor;
|
|
17
|
+
const choices = [...(descriptor.options ?? []), ...values.flatMap((value) => Array.isArray(value) ? value.map(String) : value == null ? [] : [String(value)])];
|
|
18
|
+
return { ...descriptor, optionColors: optionColorsForValues(choices, descriptor.optionColors, offset) };
|
|
19
|
+
}
|
|
20
|
+
function safeWebUrl(value) {
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(value);
|
|
23
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : undefined;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function JsonViewSchemaValueDisplay({ compact = false, decorative = false, passive = false, sourcePath, descriptor, onOpen, renderMarkdown, value, }) {
|
|
30
|
+
const sourceLiteral = useJsonSourceLiterals()(sourcePath);
|
|
31
|
+
const lossyContainer = useJsonSourceSubtreeRisk(sourcePath) && value !== null && typeof value === 'object';
|
|
32
|
+
const format = descriptor.type;
|
|
33
|
+
const choiceColors = optionColorsForValues([...(descriptor.options ?? []), ...(Array.isArray(value) ? value.map(String) : [String(value)])], descriptor.optionColors);
|
|
34
|
+
const { widgets } = useJsonViewsRegistries();
|
|
35
|
+
const CustomDisplay = widgets.getDisplay(format);
|
|
36
|
+
if (sourceLiteral !== undefined)
|
|
37
|
+
return _jsx(JsonSourceNumber, { literal: sourceLiteral });
|
|
38
|
+
if (lossyContainer)
|
|
39
|
+
return _jsx(ValueCellContent, { containerInteractive: !passive, sourcePath: sourcePath, value: value, onOpen: onOpen });
|
|
40
|
+
if (CustomDisplay && !passive)
|
|
41
|
+
return _jsx(CustomDisplay, { compact: compact, descriptor: descriptor, onOpen: onOpen, renderMarkdown: renderMarkdown, value: value });
|
|
42
|
+
if (value === undefined || value === null || value === '')
|
|
43
|
+
return _jsx("span", { className: "italic text-muted-foreground", children: "\u2014" });
|
|
44
|
+
if (compact && (format === 'body' || format === 'markdown' || format === 'html'))
|
|
45
|
+
return _jsx(ValueCellContent, { containerInteractive: !passive, sourcePath: sourcePath, value: value, onOpen: onOpen });
|
|
46
|
+
if (typeof value === 'string' && format === 'html')
|
|
47
|
+
return _jsx(HtmlPreview, { source: value, title: descriptor.title ?? 'Property' });
|
|
48
|
+
if (format === 'select')
|
|
49
|
+
return _jsx(OptionPill, { label: String(value), color: choiceColors[String(value)], className: "max-w-full", labelClassName: "truncate" });
|
|
50
|
+
if (format === 'multi-select' && Array.isArray(value)) {
|
|
51
|
+
return _jsx("span", { className: "flex min-w-0 flex-wrap gap-1", children: value.map((item, index) => _jsx(OptionPill, { label: String(item), color: choiceColors[String(item)], className: "max-w-full", labelClassName: "truncate" }, `${String(item)}-${index}`)) });
|
|
52
|
+
}
|
|
53
|
+
if (format === 'checkbox') {
|
|
54
|
+
return (_jsx("span", { role: decorative ? undefined : "checkbox", "aria-hidden": decorative || undefined, "aria-checked": decorative ? undefined : value === true, "aria-readonly": decorative ? undefined : true, "aria-label": decorative ? undefined : value === true ? 'Checked' : 'Unchecked', className: cn('inline-grid h-4 w-4 place-items-center rounded-sm border', value === true ? 'border-primary bg-primary text-primary-foreground' : 'border-border'), children: value === true && _jsx(Check, { className: "h-3 w-3" }) }));
|
|
55
|
+
}
|
|
56
|
+
if (typeof value === 'string' && descriptor.type === 'url') {
|
|
57
|
+
const href = safeWebUrl(value);
|
|
58
|
+
if (href && passive)
|
|
59
|
+
return _jsx("span", { className: "block truncate text-link", children: value });
|
|
60
|
+
if (href)
|
|
61
|
+
return _jsxs("a", { href: href, target: "_blank", rel: "noreferrer", className: "inline-flex min-w-0 items-center gap-1 text-link hover:underline", children: [_jsx(ExternalLink, { className: "h-3 w-3 shrink-0" }), _jsx("span", { className: "truncate", children: value })] });
|
|
62
|
+
}
|
|
63
|
+
if (typeof value === 'string' && descriptor.type === 'email') {
|
|
64
|
+
if (passive)
|
|
65
|
+
return _jsx("span", { className: "block truncate text-link", children: value });
|
|
66
|
+
return _jsx("a", { href: `mailto:${value}`, className: "block truncate text-link hover:underline", children: value });
|
|
67
|
+
}
|
|
68
|
+
if (typeof value === 'string' && format === 'date') {
|
|
69
|
+
const timestamp = Date.parse(`${value}T00:00:00`);
|
|
70
|
+
return Number.isNaN(timestamp) ? _jsx("span", { children: value }) : _jsx("span", { title: value, children: new Date(timestamp).toLocaleDateString(undefined, { dateStyle: 'medium' }) });
|
|
71
|
+
}
|
|
72
|
+
if (typeof value === 'string' && ((format === 'body' || format === 'markdown') || (format === 'text' && descriptor.multiline === true))) {
|
|
73
|
+
return (format === 'body' || format === 'markdown') && renderMarkdown
|
|
74
|
+
? _jsx("div", { className: "min-w-0", children: renderMarkdown(value) })
|
|
75
|
+
: _jsx("div", { "data-id": "jsonView-body-text", className: "min-w-0 whitespace-pre-wrap break-words leading-normal text-foreground", children: value });
|
|
76
|
+
}
|
|
77
|
+
return _jsx(ValueCellContent, { containerInteractive: !passive, sourcePath: sourcePath, value: value, onOpen: onOpen });
|
|
78
|
+
}
|
|
79
|
+
function sameValue(left, right) {
|
|
80
|
+
if (Object.is(left, right))
|
|
81
|
+
return true;
|
|
82
|
+
try {
|
|
83
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function SchemaEditor({ descriptor, disabled, label, minHeight, onCancel, onClear, onCommit, onOptionColorChange, renderExternalWidget, variant, value, }) {
|
|
90
|
+
const { types, widgets } = useJsonViewsRegistries();
|
|
91
|
+
const [initial] = useState(() => {
|
|
92
|
+
try {
|
|
93
|
+
return { draft: types.parse(value, descriptor), error: undefined };
|
|
94
|
+
}
|
|
95
|
+
catch (cause) {
|
|
96
|
+
return { draft: value, error: cause instanceof Error ? cause.message : 'Could not open this editor' };
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
const [draft, setDraft] = useState(initial.draft);
|
|
100
|
+
const [error, setError] = useState(initial.error);
|
|
101
|
+
const draftRef = useRef(draft);
|
|
102
|
+
const containerRef = useRef(null);
|
|
103
|
+
const committingRef = useRef(false);
|
|
104
|
+
const originalValueRef = useRef(value);
|
|
105
|
+
const attemptedCommitRef = useRef(false);
|
|
106
|
+
const baseChanged = useEditBaseChanged();
|
|
107
|
+
const fillCell = useContext(StructuredCellFillContext);
|
|
108
|
+
const format = descriptor.type;
|
|
109
|
+
const stringDraft = typeof draft === 'string' || typeof draft === 'number' || typeof draft === 'boolean'
|
|
110
|
+
? String(draft)
|
|
111
|
+
: Array.isArray(draft) ? JSON.stringify(draft) : '';
|
|
112
|
+
const commitValue = useCallback(async (candidate) => {
|
|
113
|
+
if (committingRef.current)
|
|
114
|
+
return;
|
|
115
|
+
if (baseChanged || (!attemptedCommitRef.current && !sameValue(value, originalValueRef.current))) {
|
|
116
|
+
setError('This document changed outside this editor. Cancel and reopen it before saving.');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
let next;
|
|
120
|
+
try {
|
|
121
|
+
next = sameValue(candidate, initial.draft) ? value : types.serialize(candidate, descriptor);
|
|
122
|
+
}
|
|
123
|
+
catch (cause) {
|
|
124
|
+
setError(cause instanceof Error ? cause.message : 'Could not convert this value');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (sameValue(next, value) && !error) {
|
|
128
|
+
onCancel();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const nextError = validateJsonViewSchemaValue(next, descriptor, types);
|
|
132
|
+
if (nextError) {
|
|
133
|
+
setError(nextError);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
committingRef.current = true;
|
|
137
|
+
attemptedCommitRef.current = true;
|
|
138
|
+
setError(undefined);
|
|
139
|
+
try {
|
|
140
|
+
await onCommit(next);
|
|
141
|
+
onCancel();
|
|
142
|
+
}
|
|
143
|
+
catch (commitError) {
|
|
144
|
+
setError(commitError instanceof Error ? commitError.message : 'Could not save this value');
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
committingRef.current = false;
|
|
148
|
+
}
|
|
149
|
+
}, [baseChanged, descriptor, error, initial.draft, onCancel, onCommit, types, value]);
|
|
150
|
+
const updateDraft = useCallback((next) => {
|
|
151
|
+
draftRef.current = next;
|
|
152
|
+
setDraft(next);
|
|
153
|
+
}, []);
|
|
154
|
+
const commit = useCallback(() => commitValue(draftRef.current), [commitValue]);
|
|
155
|
+
const clear = useCallback(async () => {
|
|
156
|
+
if (descriptor.required) {
|
|
157
|
+
setError('A value is required');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (baseChanged || (!attemptedCommitRef.current && !sameValue(value, originalValueRef.current))) {
|
|
161
|
+
setError('This document changed outside this editor. Cancel and reopen it before saving.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!onClear) {
|
|
165
|
+
await commitValue(null);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (committingRef.current)
|
|
169
|
+
return;
|
|
170
|
+
committingRef.current = true;
|
|
171
|
+
setError(undefined);
|
|
172
|
+
try {
|
|
173
|
+
await onClear();
|
|
174
|
+
onCancel();
|
|
175
|
+
}
|
|
176
|
+
catch (commitError) {
|
|
177
|
+
setError(commitError instanceof Error ? commitError.message : 'Could not clear this value');
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
committingRef.current = false;
|
|
181
|
+
}
|
|
182
|
+
}, [baseChanged, commitValue, descriptor.required, onCancel, onClear, value]);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
// Autofocus belongs to opening the editor, not to changing callbacks
|
|
185
|
+
// when another cell saves and the parent rerenders. Popup widgets own
|
|
186
|
+
// their focus; focusing their trigger would dismiss a non-modal popup.
|
|
187
|
+
containerRef.current
|
|
188
|
+
?.querySelector('input:not([type="hidden"]), textarea, select, button:not([aria-haspopup])')
|
|
189
|
+
?.focus({ preventScroll: (format === 'body' || format === 'markdown' || format === 'html') || variant === 'record-title' });
|
|
190
|
+
}, [format, variant]);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
const cancelOnEscape = (event) => {
|
|
193
|
+
if (event.key !== 'Escape')
|
|
194
|
+
return;
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
event.stopPropagation();
|
|
197
|
+
onCancel();
|
|
198
|
+
};
|
|
199
|
+
document.addEventListener('keydown', cancelOnEscape, true);
|
|
200
|
+
return () => document.removeEventListener('keydown', cancelOnEscape, true);
|
|
201
|
+
}, [onCancel]);
|
|
202
|
+
const Widget = widgets.get(format);
|
|
203
|
+
const control = Widget
|
|
204
|
+
? _jsx(Widget, { descriptor: descriptor, disabled: disabled, error: error, label: label, minHeight: minHeight, value: draft, stringValue: stringDraft, onCancel: onCancel, onChange: updateDraft, onClear: onClear ? () => { void clear(); } : undefined, onCommit: (candidate) => { void commitValue(candidate === undefined ? draftRef.current : candidate); }, onOptionColorChange: onOptionColorChange ? (optionValue, color) => { void onOptionColorChange(optionValue, color).catch((cause) => setError(cause instanceof Error ? cause.message : 'Could not save option color')); } : undefined })
|
|
205
|
+
: renderExternalWidget?.({ type: format, value: draft, disabled, descriptor, label, onChange: updateDraft });
|
|
206
|
+
const editor = (_jsxs("div", { ref: containerRef, "data-id": "jsonView-schema-editor", className: cn('relative min-w-0', variant === 'record-title'
|
|
207
|
+
? 'text-xl font-semibold tracking-tight [&_input]:min-h-10 [&_input]:px-1.5 [&_input]:py-1 [&_input]:font-sans [&_input]:text-xl [&_input]:font-semibold [&_input]:tracking-tight [&_textarea]:min-h-10 [&_textarea]:px-1.5 [&_textarea]:py-1 [&_textarea]:font-sans [&_textarea]:text-xl [&_textarea]:font-semibold [&_textarea]:tracking-tight'
|
|
208
|
+
: (format === 'body' || format === 'markdown' || format === 'html')
|
|
209
|
+
? 'leading-normal [&_textarea]:[font:inherit]'
|
|
210
|
+
: '[&_input]:[font:inherit] [&_textarea]:[font:inherit]'), "aria-label": `Edit ${label}`, onKeyDown: (event) => {
|
|
211
|
+
if (event.key !== 'Enter' || event.target instanceof HTMLButtonElement)
|
|
212
|
+
return;
|
|
213
|
+
const textarea = event.target instanceof HTMLTextAreaElement;
|
|
214
|
+
if (textarea && !event.metaKey && !event.ctrlKey)
|
|
215
|
+
return;
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
void commit();
|
|
218
|
+
}, children: [control, error && _jsx("p", { role: "alert", className: "absolute left-0 top-full z-[110] mt-1 rounded-md border border-destructive-border bg-destructive-surface px-2 py-1 text-[0.6875rem] text-destructive-foreground shadow-lg", children: error })] }));
|
|
219
|
+
return fillCell ? _jsx("div", { className: "px-1.5 py-1.5", children: editor }) : editor;
|
|
220
|
+
}
|
|
221
|
+
export function JsonViewSchemaValueCell({ compact = false, descriptor, initiallyEditing = false, label, onClear, onCommit, onOptionColorChange, onOpen, renderExternalWidget, renderMarkdown, saving = false, suppressRequiredValidation = false, variant = 'default', value, sourcePath, }) {
|
|
222
|
+
const [editing, setEditing] = useState(initiallyEditing);
|
|
223
|
+
const displayRef = useRef(null);
|
|
224
|
+
const [editorMinHeight, setEditorMinHeight] = useState();
|
|
225
|
+
const { types, widgets } = useJsonViewsRegistries();
|
|
226
|
+
const [quickError, setQuickError] = useState();
|
|
227
|
+
const [quickSaving, setQuickSaving] = useState(false);
|
|
228
|
+
const fillCell = useContext(StructuredCellFillContext);
|
|
229
|
+
const validationIssue = validateJsonViewSchemaValue(value, descriptor, types);
|
|
230
|
+
const invalid = suppressRequiredValidation && validationIssue === 'A value is required' ? undefined : validationIssue;
|
|
231
|
+
const warning = types.warnings(value, descriptor)[0];
|
|
232
|
+
const registration = widgets.getDefinition(descriptor.type);
|
|
233
|
+
const editable = Boolean(onCommit && (registration?.editor || renderExternalWidget));
|
|
234
|
+
const quickEdit = registration?.quickEdit;
|
|
235
|
+
const stableText = ((descriptor.type === 'body' || descriptor.type === 'markdown' || descriptor.type === 'html') || (descriptor.type === 'text' && descriptor.multiline === true)) && !compact;
|
|
236
|
+
const choiceLayout = descriptor.type === 'select' || descriptor.type === 'multi-select';
|
|
237
|
+
// A wrapped choice preview must not shrink while its popup is open:
|
|
238
|
+
// dismissing it on pointerdown would move the next row before the click.
|
|
239
|
+
const stableLayout = stableText || variant === 'record-title' || choiceLayout;
|
|
240
|
+
const preserveLayoutHeight = () => {
|
|
241
|
+
if (!stableLayout)
|
|
242
|
+
return;
|
|
243
|
+
const height = displayRef.current?.getBoundingClientRect().height ?? 0;
|
|
244
|
+
if (height > 0)
|
|
245
|
+
setEditorMinHeight(height);
|
|
246
|
+
};
|
|
247
|
+
const stopEditing = () => {
|
|
248
|
+
// Height is only reserved while editing; the preview must size naturally.
|
|
249
|
+
setEditorMinHeight(undefined);
|
|
250
|
+
setEditing(false);
|
|
251
|
+
};
|
|
252
|
+
if (quickEdit && onCommit) {
|
|
253
|
+
const checked = value === true;
|
|
254
|
+
return (_jsxs("div", { className: cn('h-full min-w-0', invalid && 'ring-1 ring-inset ring-destructive'), title: invalid, children: [_jsx("button", { type: "button", "data-id": "jsonView-checkbox-toggle", role: typeof value === 'boolean' ? 'checkbox' : undefined, "aria-label": typeof value === 'boolean' ? `${checked ? 'Uncheck' : 'Check'} ${label}` : `Edit ${label}`, "aria-checked": typeof value === 'boolean' ? checked : undefined, disabled: saving || quickSaving, className: cn('flex w-full items-center transition-colors hover:bg-primary/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring disabled:opacity-50 dark:hover:bg-primary/25', fillCell ? 'h-full min-h-11 px-3 py-2' : 'min-h-7 rounded-md px-1.5'), onClick: () => {
|
|
255
|
+
void (async () => {
|
|
256
|
+
if (quickSaving)
|
|
257
|
+
return;
|
|
258
|
+
setQuickSaving(true);
|
|
259
|
+
setQuickError(undefined);
|
|
260
|
+
try {
|
|
261
|
+
const next = types.serialize(quickEdit(types.parse(value, descriptor)), descriptor);
|
|
262
|
+
const problem = validateJsonViewSchemaValue(next, descriptor, types);
|
|
263
|
+
if (problem)
|
|
264
|
+
throw new Error(problem);
|
|
265
|
+
await onCommit(next);
|
|
266
|
+
}
|
|
267
|
+
catch (cause) {
|
|
268
|
+
setQuickError(cause instanceof Error ? cause.message : 'Could not save this value');
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
setQuickSaving(false);
|
|
272
|
+
}
|
|
273
|
+
})();
|
|
274
|
+
}, children: _jsx(JsonViewSchemaValueDisplay, { sourcePath: sourcePath, decorative: true, descriptor: descriptor, value: value, onOpen: onOpen }) }), quickError && _jsx("p", { role: "alert", className: "text-xs text-destructive", children: quickError })] }));
|
|
275
|
+
}
|
|
276
|
+
if (editing && editable && onCommit) {
|
|
277
|
+
const editor = _jsx(SchemaEditor, { descriptor: descriptor, disabled: saving, label: label, minHeight: editorMinHeight, value: value, onCancel: stopEditing, onClear: onClear, onCommit: onCommit, onOptionColorChange: onOptionColorChange, renderExternalWidget: renderExternalWidget, variant: variant }, `${descriptor.type}:${types.version}`);
|
|
278
|
+
return stableLayout
|
|
279
|
+
? _jsx("div", { ref: displayRef, "data-id": "jsonView-schema-value", className: "h-full min-w-0", style: editorMinHeight ? { minHeight: editorMinHeight } : undefined, children: editor })
|
|
280
|
+
: editor;
|
|
281
|
+
}
|
|
282
|
+
const container = value !== null && typeof value === 'object' && descriptor.type !== 'multi-select';
|
|
283
|
+
const textPreview = compact && (descriptor.type === 'body' || descriptor.type === 'markdown' || descriptor.type === 'html');
|
|
284
|
+
const linkedValue = typeof value === 'string'
|
|
285
|
+
&& (descriptor.type === 'url' || descriptor.type === 'email');
|
|
286
|
+
const interactiveText = ((descriptor.type === 'body' || descriptor.type === 'markdown') && renderMarkdown !== undefined || descriptor.type === 'html') && !compact && typeof value === 'string';
|
|
287
|
+
const explicitEditAction = linkedValue || descriptor.type === 'html';
|
|
288
|
+
const startEditing = () => {
|
|
289
|
+
preserveLayoutHeight();
|
|
290
|
+
setEditing(true);
|
|
291
|
+
};
|
|
292
|
+
const actions = (_jsxs(_Fragment, { children: [explicitEditAction && editable && _jsx(EditValueAction, { disabled: saving, label: label, onClick: startEditing }), (container || textPreview) && _jsx(ExpandValueButton, { onClick: onOpen })] }));
|
|
293
|
+
return (_jsx("div", { ref: displayRef, "data-id": "jsonView-schema-value", className: cn('h-full min-w-0', invalid && 'ring-1 ring-inset ring-destructive', !invalid && warning && 'ring-1 ring-inset ring-amber-500'), style: stableLayout && editorMinHeight ? { minHeight: editorMinHeight } : undefined, title: invalid ?? warning, children: _jsx(StructuredValueCellFrame, { actions: explicitEditAction || container || textPreview ? actions : undefined, overlayActions: descriptor.type === 'html' && !compact, activationDisabled: !editable || saving || explicitEditAction, activationLabel: editable && !explicitEditAction ? `Edit ${label}` : undefined, activationRole: interactiveText ? 'group' : 'button', className: variant === 'record-title' ? 'min-h-10 w-fit max-w-full px-1.5 py-1' : stableText ? 'px-0' : undefined, onActivate: editable && !explicitEditAction ? startEditing : undefined, children: stableText ? (_jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-1.5", children: [_jsx(JsonViewSchemaValueDisplay, { sourcePath: sourcePath, compact: compact, descriptor: descriptor, value: value, onOpen: onOpen, renderMarkdown: renderMarkdown }), (invalid || warning) && _jsx(AlertTriangle, { "aria-label": invalid ?? warning, className: cn('h-3.5 w-3.5 shrink-0', invalid ? 'text-destructive' : 'text-amber-600 dark:text-amber-300') })] })) : (_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx(JsonViewSchemaValueDisplay, { sourcePath: sourcePath, compact: compact, descriptor: descriptor, value: value, onOpen: onOpen, renderMarkdown: renderMarkdown }), (invalid || warning) && _jsx(AlertTriangle, { "aria-label": invalid ?? warning, className: cn('h-3.5 w-3.5 shrink-0', invalid ? 'text-destructive' : 'text-amber-600 dark:text-amber-300') })] })) }) }));
|
|
294
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { prefersSource } from '@script-it/json-views-core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { prefersSource } from '@script-it/json-views-core';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { inspectJsonSource, type ValuePath } from '@script-it/json-views-core';
|
|
2
|
+
type SourceDiagnostic = ReturnType<typeof inspectJsonSource>['diagnostics'][number];
|
|
3
|
+
export declare function replacementIssue(diagnostics: readonly SourceDiagnostic[], path: ValuePath): SourceDiagnostic | undefined;
|
|
4
|
+
/** Source-preserving mutations must still address an unambiguous JSON location. */
|
|
5
|
+
export declare function assertUnambiguousSourcePath(source: string, paths: readonly ValuePath[]): void;
|
|
6
|
+
/** Replacing a subtree would serialize all its numbers and duplicate properties. */
|
|
7
|
+
export declare function assertSourceReplacementSafe(source: string, paths: readonly ValuePath[]): void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { inspectJsonSource } from '@script-it/json-views-core';
|
|
2
|
+
function containsPath(parent, child) {
|
|
3
|
+
return parent.length <= child.length && parent.every((segment, index) => segment === child[index]);
|
|
4
|
+
}
|
|
5
|
+
export function replacementIssue(diagnostics, path) {
|
|
6
|
+
return diagnostics.find((issue) => containsPath(path, issue.sourcePath)
|
|
7
|
+
|| (issue.code === 'duplicate-key' && containsPath(issue.sourcePath, path)));
|
|
8
|
+
}
|
|
9
|
+
/** Source-preserving mutations must still address an unambiguous JSON location. */
|
|
10
|
+
export function assertUnambiguousSourcePath(source, paths) {
|
|
11
|
+
const issue = inspectJsonSource(source).diagnostics.find((diagnostic) => diagnostic.code === 'duplicate-key'
|
|
12
|
+
&& paths.some((path) => containsPath(diagnostic.sourcePath, path)));
|
|
13
|
+
if (issue)
|
|
14
|
+
throw new Error('This path contains a duplicate property name. Edit the JSON source to disambiguate it first.');
|
|
15
|
+
}
|
|
16
|
+
/** Replacing a subtree would serialize all its numbers and duplicate properties. */
|
|
17
|
+
export function assertSourceReplacementSafe(source, paths) {
|
|
18
|
+
const diagnostics = inspectJsonSource(source).diagnostics;
|
|
19
|
+
if (paths.some((path) => replacementIssue(diagnostics, path))) {
|
|
20
|
+
throw new Error('This value contains a number or duplicate property that cannot be represented losslessly. Edit the JSON source for this value.');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { inspectJsonSource, ValuePath } from '@script-it/json-views-core';
|
|
3
|
+
type SourceDiagnostic = ReturnType<typeof inspectJsonSource>['diagnostics'][number];
|
|
4
|
+
type LiteralLookup = (path?: ValuePath | null) => string | undefined;
|
|
5
|
+
/** The original number token stays authoritative when JavaScript cannot represent it. */
|
|
6
|
+
export declare function JsonSourceLiteralsProvider({ diagnostics, children }: {
|
|
7
|
+
diagnostics: readonly SourceDiagnostic[];
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export declare function useJsonSourceLiterals(): LiteralLookup;
|
|
11
|
+
export declare function useJsonSourceSubtreeRisk(path?: ValuePath): boolean;
|
|
12
|
+
export declare function JsonSourceNumber({ literal }: {
|
|
13
|
+
literal: string;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
3
|
+
const SourceLiteralsContext = createContext({ lookup: () => undefined, containsRisk: () => false });
|
|
4
|
+
/** The original number token stays authoritative when JavaScript cannot represent it. */
|
|
5
|
+
export function JsonSourceLiteralsProvider({ diagnostics, children }) {
|
|
6
|
+
const literalsContext = useMemo(() => {
|
|
7
|
+
const literals = new Map(diagnostics.filter((issue) => issue.code === 'unsafe-number' && !issue.shadowed)
|
|
8
|
+
.map((issue) => [JSON.stringify(issue.sourcePath), issue.token]));
|
|
9
|
+
return {
|
|
10
|
+
lookup: (path) => path == null ? undefined : literals.get(JSON.stringify(path)),
|
|
11
|
+
containsRisk: (path) => path !== undefined && diagnostics.some((issue) => path.length <= issue.sourcePath.length && path.every((part, index) => part === issue.sourcePath[index])),
|
|
12
|
+
};
|
|
13
|
+
}, [diagnostics]);
|
|
14
|
+
return _jsx(SourceLiteralsContext.Provider, { value: literalsContext, children: children });
|
|
15
|
+
}
|
|
16
|
+
export function useJsonSourceLiterals() {
|
|
17
|
+
return useContext(SourceLiteralsContext).lookup;
|
|
18
|
+
}
|
|
19
|
+
export function useJsonSourceSubtreeRisk(path) {
|
|
20
|
+
return useContext(SourceLiteralsContext).containsRisk(path);
|
|
21
|
+
}
|
|
22
|
+
export function JsonSourceNumber({ literal }) {
|
|
23
|
+
return _jsx("span", { "data-id": "jsonView-exact-source-number", className: "min-w-0 break-all font-mono text-foreground", title: "Exact numeric literal from JSON source", children: literal });
|
|
24
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Component, type ReactNode } from 'react';
|
|
2
|
+
/** Keeps the original source recoverable when annotations or a plugin fail. */
|
|
3
|
+
export declare function JsonSourceRecovery({ source, error, onCommit }: {
|
|
4
|
+
source: string;
|
|
5
|
+
error: string;
|
|
6
|
+
onCommit?: (source: string) => Promise<void>;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
export declare class JsonRendererBoundary extends Component<{
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
source: string;
|
|
11
|
+
onCommit?: (source: string) => Promise<void>;
|
|
12
|
+
}, {
|
|
13
|
+
error?: string;
|
|
14
|
+
source?: string;
|
|
15
|
+
}> {
|
|
16
|
+
state: {
|
|
17
|
+
error?: string;
|
|
18
|
+
source?: string;
|
|
19
|
+
};
|
|
20
|
+
static getDerivedStateFromProps(props: {
|
|
21
|
+
source: string;
|
|
22
|
+
}, state: {
|
|
23
|
+
source?: string;
|
|
24
|
+
}): {
|
|
25
|
+
source: string;
|
|
26
|
+
error: undefined;
|
|
27
|
+
} | null;
|
|
28
|
+
static getDerivedStateFromError(cause: unknown): {
|
|
29
|
+
error: string;
|
|
30
|
+
};
|
|
31
|
+
render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
32
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Component, useState } from 'react';
|
|
3
|
+
/** Keeps the original source recoverable when annotations or a plugin fail. */
|
|
4
|
+
export function JsonSourceRecovery({ source, error, onCommit }) {
|
|
5
|
+
const [draft, setDraft] = useState(source);
|
|
6
|
+
const [saving, setSaving] = useState(false);
|
|
7
|
+
const [saveError, setSaveError] = useState();
|
|
8
|
+
return _jsxs("section", { className: "grid min-h-0 gap-3 p-4", children: [_jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }), _jsxs("label", { className: "grid min-h-0 gap-2 text-sm", children: ["JSON source", _jsx("textarea", { "aria-label": "JSON source", className: "min-h-64 w-full rounded-md border border-border bg-background p-3 font-mono text-xs text-foreground", value: draft, readOnly: !onCommit, onChange: (event) => setDraft(event.target.value) })] }), onCommit && _jsx("button", { type: "button", disabled: saving, className: "w-fit rounded-md bg-primary px-3 py-2 text-sm text-primary-foreground disabled:opacity-50", onClick: () => {
|
|
9
|
+
void (async () => {
|
|
10
|
+
setSaving(true);
|
|
11
|
+
setSaveError(undefined);
|
|
12
|
+
try {
|
|
13
|
+
JSON.parse(draft);
|
|
14
|
+
await onCommit(draft);
|
|
15
|
+
}
|
|
16
|
+
catch (cause) {
|
|
17
|
+
setSaveError(cause instanceof Error ? cause.message : 'Could not save the source');
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
setSaving(false);
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
}, children: saving ? 'Saving…' : 'Save JSON source' }), saveError && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: saveError })] });
|
|
24
|
+
}
|
|
25
|
+
export class JsonRendererBoundary extends Component {
|
|
26
|
+
state = { source: this.props.source };
|
|
27
|
+
static getDerivedStateFromProps(props, state) {
|
|
28
|
+
return props.source !== state.source ? { source: props.source, error: undefined } : null;
|
|
29
|
+
}
|
|
30
|
+
static getDerivedStateFromError(cause) {
|
|
31
|
+
return { error: cause instanceof Error ? cause.message : 'Could not render this JSON' };
|
|
32
|
+
}
|
|
33
|
+
render() {
|
|
34
|
+
return this.state.error
|
|
35
|
+
? _jsx(JsonSourceRecovery, { source: this.props.source, error: `The viewer could not render this document: ${this.state.error}`, onCommit: this.props.onCommit })
|
|
36
|
+
: this.props.children;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** A whole-data view is context, not a selection. Root metadata does not count as data. */
|
|
2
|
+
export function coversAllDocumentData(root, paths) {
|
|
3
|
+
const covered = (value, path) => {
|
|
4
|
+
if (paths.some((selected) => selected.length <= path.length && selected.every((part, index) => part === path[index])))
|
|
5
|
+
return true;
|
|
6
|
+
if (value === null || typeof value !== 'object')
|
|
7
|
+
return false;
|
|
8
|
+
const keys = Array.isArray(value)
|
|
9
|
+
? value.map((_, index) => index)
|
|
10
|
+
: Object.keys(value).filter((key) => path.length > 0 || key !== '$jsonviews');
|
|
11
|
+
if (keys.length === 0)
|
|
12
|
+
return false;
|
|
13
|
+
return keys.every((key) => covered(value[key], [...path, key]));
|
|
14
|
+
};
|
|
15
|
+
return covered(root, []);
|
|
16
|
+
}
|
|
@@ -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 ProjectedTableView({ compiled, editing, filePath, copyFileName, fillHeight, onSaveView, onCurrentPathChange, query, rawView, renderExternalWidget, renderMarkdown, uiSize, view, }: {
|
|
7
|
+
compiled: CompiledJsonViewMetadata;
|
|
8
|
+
editing?: JsonViewJsonEditing;
|
|
9
|
+
filePath?: string;
|
|
10
|
+
copyFileName: string;
|
|
11
|
+
fillHeight: boolean;
|
|
12
|
+
onSaveView?: (value: Record<string, unknown>) => Promise<void>;
|
|
13
|
+
onCurrentPathChange?: JsonViewerProps['onCurrentPathChange'];
|
|
14
|
+
query: string;
|
|
15
|
+
rawView?: Record<string, unknown>;
|
|
16
|
+
renderExternalWidget?: JsonViewSchemaExternalWidgetRenderer;
|
|
17
|
+
renderMarkdown?: (content: string) => ReactNode;
|
|
18
|
+
uiSize?: UiSize;
|
|
19
|
+
view: CompiledJsonViewView;
|
|
20
|
+
}): import("react").JSX.Element;
|