@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,179 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
// Measure the stationary columns once. Hovering never changes their geometry or
|
|
3
|
+
// renders the cards; only the insertion marker moves, at most once per frame.
|
|
4
|
+
export function useKanbanColumnDrag({ enabled, scope, onMove }) {
|
|
5
|
+
const boardRef = useRef(null);
|
|
6
|
+
const markerRef = useRef(null);
|
|
7
|
+
const sessionRef = useRef(undefined);
|
|
8
|
+
const moveRef = useRef(onMove);
|
|
9
|
+
moveRef.current = onMove;
|
|
10
|
+
function clear() {
|
|
11
|
+
const session = sessionRef.current;
|
|
12
|
+
sessionRef.current = undefined;
|
|
13
|
+
boardRef.current?.removeAttribute('data-column-reordering');
|
|
14
|
+
if (markerRef.current)
|
|
15
|
+
markerRef.current.hidden = true;
|
|
16
|
+
if (!session)
|
|
17
|
+
return;
|
|
18
|
+
if (session.frame !== undefined)
|
|
19
|
+
cancelAnimationFrame(session.frame);
|
|
20
|
+
session.source.removeAttribute('data-column-dragging');
|
|
21
|
+
session.cleanup();
|
|
22
|
+
}
|
|
23
|
+
useEffect(() => clear, [scope, enabled]);
|
|
24
|
+
function destination(session) {
|
|
25
|
+
const board = boardRef.current;
|
|
26
|
+
if (!board)
|
|
27
|
+
return;
|
|
28
|
+
const bounds = board.getBoundingClientRect();
|
|
29
|
+
if (session.x < bounds.left || session.x > bounds.right || session.y < bounds.top || session.y > bounds.bottom)
|
|
30
|
+
return;
|
|
31
|
+
const x = session.x - bounds.left + board.scrollLeft;
|
|
32
|
+
const remaining = session.columns.filter(column => column.key !== session.key);
|
|
33
|
+
let index = remaining.findIndex(column => x < (column.left + column.right) / 2);
|
|
34
|
+
if (index < 0)
|
|
35
|
+
index = remaining.length;
|
|
36
|
+
// Dropping back in the original slot is a no-op, including either adjacent gap.
|
|
37
|
+
if (index === session.columns.findIndex(column => column.key === session.key))
|
|
38
|
+
return;
|
|
39
|
+
const next = remaining[index];
|
|
40
|
+
const previous = remaining[index - 1];
|
|
41
|
+
if (next)
|
|
42
|
+
return { key: next.key, position: 'before', x: Math.max(2, next.left - 6) };
|
|
43
|
+
if (previous)
|
|
44
|
+
return { key: previous.key, position: 'after', x: previous.right + 6 };
|
|
45
|
+
}
|
|
46
|
+
function paint(time) {
|
|
47
|
+
const session = sessionRef.current;
|
|
48
|
+
const board = boardRef.current;
|
|
49
|
+
const marker = markerRef.current;
|
|
50
|
+
if (!session || !board || !marker)
|
|
51
|
+
return;
|
|
52
|
+
session.frame = undefined;
|
|
53
|
+
const bounds = board.getBoundingClientRect();
|
|
54
|
+
const inside = session.x >= bounds.left && session.x <= bounds.right && session.y >= bounds.top && session.y <= bounds.bottom;
|
|
55
|
+
const edge = Math.min(64, bounds.width / 4);
|
|
56
|
+
const speed = !inside ? 0 : session.x < bounds.left + edge
|
|
57
|
+
? -Math.min(1, (bounds.left + edge - session.x) / edge)
|
|
58
|
+
: session.x > bounds.right - edge ? Math.min(1, (session.x - bounds.right + edge) / edge) : 0;
|
|
59
|
+
const elapsed = Math.min(32, session.previousTime === undefined ? 16 : time - session.previousTime);
|
|
60
|
+
session.previousTime = time;
|
|
61
|
+
const beforeScroll = board.scrollLeft;
|
|
62
|
+
if (speed)
|
|
63
|
+
board.scrollLeft += speed * elapsed * 0.65;
|
|
64
|
+
const target = destination(session);
|
|
65
|
+
marker.hidden = !target;
|
|
66
|
+
if (target) {
|
|
67
|
+
marker.style.transform = `translateX(${target.x}px)`;
|
|
68
|
+
marker.style.top = `${board.scrollTop + 12}px`;
|
|
69
|
+
marker.style.height = `${Math.max(40, Math.min(board.clientHeight - 24, session.height))}px`;
|
|
70
|
+
marker.dataset.position = target.position;
|
|
71
|
+
marker.dataset.target = target.key;
|
|
72
|
+
}
|
|
73
|
+
if (speed && beforeScroll !== board.scrollLeft)
|
|
74
|
+
session.frame = requestAnimationFrame(paint);
|
|
75
|
+
}
|
|
76
|
+
function schedule() {
|
|
77
|
+
const session = sessionRef.current;
|
|
78
|
+
if (session && session.frame === undefined)
|
|
79
|
+
session.frame = requestAnimationFrame(paint);
|
|
80
|
+
}
|
|
81
|
+
function start(event, key) {
|
|
82
|
+
if (!enabled || !boardRef.current) {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
clear();
|
|
87
|
+
const board = boardRef.current;
|
|
88
|
+
const bounds = board.getBoundingClientRect();
|
|
89
|
+
const columns = Array.from(board.querySelectorAll('[data-id="jsonView-kanban-column"]')).map(element => {
|
|
90
|
+
const rect = element.getBoundingClientRect();
|
|
91
|
+
return { key: element.dataset.groupKey, element, left: rect.left - bounds.left + board.scrollLeft, right: rect.right - bounds.left + board.scrollLeft };
|
|
92
|
+
});
|
|
93
|
+
const source = columns.find(column => column.key === key)?.element;
|
|
94
|
+
if (!source) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
event.stopPropagation();
|
|
99
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
100
|
+
event.dataTransfer.setData('application/x-jsonView-kanban-group', key);
|
|
101
|
+
// A compact, opaque header preview follows the cursor instead of a tiny grip
|
|
102
|
+
// or a ghost containing hundreds of cards.
|
|
103
|
+
const header = event.currentTarget;
|
|
104
|
+
const ghost = header.cloneNode(true);
|
|
105
|
+
ghost.setAttribute('aria-hidden', 'true');
|
|
106
|
+
ghost.removeAttribute('data-id');
|
|
107
|
+
ghost.className = 'flex items-center justify-between gap-2 rounded-md border border-border bg-card px-3 text-xs font-medium text-foreground shadow-lg';
|
|
108
|
+
Object.assign(ghost.style, { position: 'fixed', top: '-10000px', left: '0', width: `${source.offsetWidth}px`, height: '40px', pointerEvents: 'none' });
|
|
109
|
+
board.appendChild(ghost);
|
|
110
|
+
event.dataTransfer.setDragImage?.(ghost, Math.max(0, Math.min(source.offsetWidth, event.clientX - source.getBoundingClientRect().left)), 20);
|
|
111
|
+
source.setAttribute('data-column-dragging', 'true');
|
|
112
|
+
board.setAttribute('data-column-reordering', 'true');
|
|
113
|
+
const doc = board.ownerDocument;
|
|
114
|
+
const escape = (event) => { if (event.key === 'Escape')
|
|
115
|
+
clear(); };
|
|
116
|
+
const leaveWindow = () => clear();
|
|
117
|
+
doc.addEventListener('keydown', escape, true);
|
|
118
|
+
doc.addEventListener('dragend', clear);
|
|
119
|
+
doc.addEventListener('drop', clear);
|
|
120
|
+
doc.defaultView?.addEventListener('blur', leaveWindow);
|
|
121
|
+
board.addEventListener('scroll', schedule, { passive: true });
|
|
122
|
+
sessionRef.current = {
|
|
123
|
+
key, source, columns, height: Math.max(...columns.map(column => column.element.offsetHeight)), x: event.clientX, y: event.clientY,
|
|
124
|
+
cleanup: () => {
|
|
125
|
+
ghost.remove();
|
|
126
|
+
doc.removeEventListener('keydown', escape, true);
|
|
127
|
+
doc.removeEventListener('dragend', clear);
|
|
128
|
+
doc.removeEventListener('drop', clear);
|
|
129
|
+
doc.defaultView?.removeEventListener('blur', leaveWindow);
|
|
130
|
+
board.removeEventListener('scroll', schedule);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
boardRef, markerRef, start,
|
|
136
|
+
onDragEnterCapture(event) {
|
|
137
|
+
if (!sessionRef.current)
|
|
138
|
+
return;
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
event.stopPropagation();
|
|
141
|
+
},
|
|
142
|
+
// Capture prevents card drop handlers from swallowing a column drop. The
|
|
143
|
+
// entire board, including gaps and the space below short lanes, is a target.
|
|
144
|
+
onDragOverCapture(event) {
|
|
145
|
+
const session = sessionRef.current;
|
|
146
|
+
if (!session)
|
|
147
|
+
return;
|
|
148
|
+
event.preventDefault();
|
|
149
|
+
event.stopPropagation();
|
|
150
|
+
event.dataTransfer.dropEffect = 'move';
|
|
151
|
+
session.x = event.clientX;
|
|
152
|
+
session.y = event.clientY;
|
|
153
|
+
schedule();
|
|
154
|
+
},
|
|
155
|
+
onDropCapture(event) {
|
|
156
|
+
const session = sessionRef.current;
|
|
157
|
+
if (!session)
|
|
158
|
+
return;
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
session.x = event.clientX;
|
|
162
|
+
session.y = event.clientY;
|
|
163
|
+
const target = destination(session);
|
|
164
|
+
clear();
|
|
165
|
+
if (target)
|
|
166
|
+
moveRef.current(session.key, target.key, target.position);
|
|
167
|
+
},
|
|
168
|
+
onDragLeave(event) {
|
|
169
|
+
if (!sessionRef.current || (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)))
|
|
170
|
+
return;
|
|
171
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
172
|
+
if (event.clientX > bounds.left && event.clientX < bounds.right && event.clientY > bounds.top && event.clientY < bounds.bottom)
|
|
173
|
+
return;
|
|
174
|
+
sessionRef.current.x = -Infinity;
|
|
175
|
+
if (markerRef.current)
|
|
176
|
+
markerRef.current.hidden = true;
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AlertTriangle } from 'lucide-react';
|
|
3
|
+
import {} from '@script-it/json-views-core';
|
|
4
|
+
import { pathLabel } from './view-model.js';
|
|
5
|
+
function groupKey(item) {
|
|
6
|
+
return JSON.stringify([item.scope, item.code, item.message, item.severity, item.metadataPath, item.declaration, item.viewId]);
|
|
7
|
+
}
|
|
8
|
+
export function Diagnostics({ compiled }) {
|
|
9
|
+
if (compiled.diagnostics.length === 0)
|
|
10
|
+
return null;
|
|
11
|
+
const groups = Array.from(compiled.diagnostics.reduce((result, item) => {
|
|
12
|
+
const key = groupKey(item);
|
|
13
|
+
const existing = result.get(key);
|
|
14
|
+
if (existing) {
|
|
15
|
+
existing.items.push(item);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
result.set(key, { item, items: [item] });
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}, new Map()).values());
|
|
22
|
+
return (_jsxs("details", { "data-id": "jsonView-json-diagnostics", className: "relative shrink-0 text-xs", children: [_jsxs("summary", { "aria-label": `${groups.length} JSON ${groups.length === 1 ? 'issue' : 'issues'}`, title: `${groups.length} JSON ${groups.length === 1 ? 'issue' : 'issues'}`, className: "flex h-7 cursor-pointer list-none items-center gap-1 rounded-md px-1.5 text-warning hover:bg-accent", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[10px] tabular-nums", children: groups.length })] }), _jsxs("div", { className: "absolute right-0 top-8 z-30 max-h-[min(24rem,60vh)] w-[min(28rem,calc(100vw-2rem))] overflow-y-auto rounded-lg border border-border bg-card p-3 text-foreground shadow-lg", children: [_jsx("div", { className: "font-medium", children: "JSON issues" }), _jsx("ul", { className: "mt-2 grid gap-2", children: groups.map(({ item, items }) => {
|
|
23
|
+
const examples = items.flatMap((entry) => entry.sourcePath ? [pathLabel(entry.sourcePath)] : []).slice(0, 2);
|
|
24
|
+
return (_jsxs("li", { className: "min-w-0", children: [_jsxs("div", { children: [item.message, items.length > 1 ? ` (${items.length})` : ''] }), _jsx("div", { className: "mt-0.5 truncate font-mono text-[10px] text-muted-foreground", children: item.declaration ?? pathLabel(item.metadataPath) }), examples.length > 0 && _jsx("div", { className: "mt-0.5 truncate font-mono text-[10px] text-muted-foreground", children: examples.join(', ') }), item.help && (_jsxs("details", { className: "mt-1 rounded border border-border p-2", children: [_jsx("summary", { "aria-label": `How to fix: ${item.message}`, className: "cursor-pointer font-medium", children: "How to fix" }), _jsxs("div", { className: "mt-2 grid gap-2 break-words", children: [_jsx("div", { children: item.help.fix }), _jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Expected: " }), item.help.expected] }), _jsxs("div", { className: "font-mono text-[10px]", children: [item.metadataSource === 'external' ? 'External annotations: ' : 'Annotation: ', pathLabel(item.metadataPath)] }), item.help.received !== undefined && _jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Received: " }), _jsx("code", { children: item.help.received })] }), item.help.allowedValues && _jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Supported: " }), item.help.allowedValues.length ? item.help.allowedValues.join(', ') : 'No shared supported values; adjust the field types or remove this configuration.'] }), item.help.examples && _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Examples" }), _jsx("pre", { className: "mt-1 whitespace-pre-wrap rounded bg-muted p-2 text-[10px]", children: item.help.examples.map((example) => JSON.stringify(example, null, 2)).join('\n') })] }), item.help.capabilities && _jsx("ul", { className: "list-disc space-y-1 pl-4", children: item.help.capabilities.map((capability) => _jsx("li", { children: capability }, capability)) }), items.some((entry) => entry.sourcePath) && (_jsxs("div", { children: [_jsxs("div", { className: "font-medium", children: ["Affected data", items.length > 1 ? ` (${items.length})` : ''] }), _jsx("ul", { className: "mt-1 grid max-h-40 gap-1 overflow-y-auto font-mono text-[10px]", children: items.map((entry, index) => entry.sourcePath && _jsxs("li", { children: [pathLabel(entry.sourcePath), entry.scope === 'value' && entry.help?.received !== undefined ? `: ${entry.help.received}` : ''] }, index)) })] })), _jsxs("div", { className: "text-[10px] text-muted-foreground", children: [item.severity ?? 'error', " \u00B7 ", item.code] })] })] }))] }, groupKey(item)));
|
|
25
|
+
}) })] })] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ValuePath } from '@script-it/json-views-core';
|
|
2
|
+
import { type CompiledJsonViewMetadata, type CompiledJsonViewView, type JsonViewPath, type JsonViewSchemaDescriptor, type JsonViewViewRow } from '@script-it/json-views-core';
|
|
3
|
+
export declare function isRecordCollection(value: unknown): boolean;
|
|
4
|
+
export declare function presentsRecordCollection(view: CompiledJsonViewView, forceTable?: boolean): boolean;
|
|
5
|
+
export declare function matchesSearch(value: unknown, query: string): boolean;
|
|
6
|
+
export declare function rawViewsFor(compiled: CompiledJsonViewMetadata): unknown[];
|
|
7
|
+
export declare function rawViewFor(compiled: CompiledJsonViewMetadata, view: CompiledJsonViewView): Record<string, unknown> | undefined;
|
|
8
|
+
export declare function uniqueViewName(label: string, views: readonly unknown[]): string;
|
|
9
|
+
export declare function uniqueViewId(name: string, views: readonly unknown[]): string;
|
|
10
|
+
export declare function inferredDescriptor(value: unknown): JsonViewSchemaDescriptor;
|
|
11
|
+
export declare function jsonPathForValuePath(path: ValuePath): string;
|
|
12
|
+
export declare function rowHasLongText(compiled: CompiledJsonViewMetadata, rowPath: ValuePath): boolean;
|
|
13
|
+
export interface RowTitleField {
|
|
14
|
+
label: string;
|
|
15
|
+
path: ValuePath;
|
|
16
|
+
value: unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare function titleFieldForRow(root: unknown, view: CompiledJsonViewView, row: JsonViewViewRow): RowTitleField | undefined;
|
|
19
|
+
export declare function nestedRecordView(root: unknown, rowPath: ValuePath, titleKey: string | undefined, name: string): {
|
|
20
|
+
row: JsonViewViewRow;
|
|
21
|
+
view: CompiledJsonViewView;
|
|
22
|
+
} | undefined;
|
|
23
|
+
export declare function titleForRow(root: unknown, view: CompiledJsonViewView, row: JsonViewViewRow): string;
|
|
24
|
+
export declare function rowAtSourcePath(root: unknown, sourcePath: ValuePath | undefined): JsonViewViewRow | undefined;
|
|
25
|
+
export declare function rowForPage(view: CompiledJsonViewView): JsonViewViewRow | undefined;
|
|
26
|
+
export declare function pathLabel(path: ValuePath): string;
|
|
27
|
+
export declare function sameJsonViewPath(left: JsonViewPath, right: JsonViewPath | undefined): boolean;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { schemaForJsonViewPath, getValueAtPath, isJsonViewTableCandidate, VALUE_PATH_MISSING } from '@script-it/json-views-core';
|
|
2
|
+
import { JSON_VIEW_PATH_MISSING, inferJsonViewRecordTitleKey, parseJsonViewPath, resolveJsonViewRowPath } from '@script-it/json-views-core';
|
|
3
|
+
import { isLongText } from './long-text.js';
|
|
4
|
+
export function isRecordCollection(value) {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.every((item) => item !== null && typeof item === 'object' && !Array.isArray(item));
|
|
7
|
+
return value !== null
|
|
8
|
+
&& typeof value === 'object'
|
|
9
|
+
&& !Array.isArray(value)
|
|
10
|
+
&& Object.values(value).every((item) => item !== null && typeof item === 'object' && !Array.isArray(item));
|
|
11
|
+
}
|
|
12
|
+
export function presentsRecordCollection(view, forceTable = false) {
|
|
13
|
+
if (!isRecordCollection(view.value))
|
|
14
|
+
return false;
|
|
15
|
+
if (view.display === 'kanban')
|
|
16
|
+
return true;
|
|
17
|
+
if (view.declarationIndex < 0 && !forceTable)
|
|
18
|
+
return Array.isArray(view.value) && isJsonViewTableCandidate(view.value);
|
|
19
|
+
return view.columns !== undefined
|
|
20
|
+
|| view.filter !== undefined
|
|
21
|
+
|| view.sort.length > 0
|
|
22
|
+
|| (Array.isArray(view.value)
|
|
23
|
+
? view.value.length > 0
|
|
24
|
+
: Object.keys(view.value).length > 0);
|
|
25
|
+
}
|
|
26
|
+
export function matchesSearch(value, query) {
|
|
27
|
+
const normalized = query.trim().toLocaleLowerCase();
|
|
28
|
+
if (!normalized)
|
|
29
|
+
return true;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.stringify(value).toLocaleLowerCase().includes(normalized);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return String(value).toLocaleLowerCase().includes(normalized);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function rawViewsFor(compiled) {
|
|
38
|
+
const metadata = compiled.metadata;
|
|
39
|
+
if (metadata !== null && typeof metadata === 'object' && !Array.isArray(metadata)) {
|
|
40
|
+
const views = metadata.views;
|
|
41
|
+
if (Array.isArray(views))
|
|
42
|
+
return views;
|
|
43
|
+
}
|
|
44
|
+
return compiled.inference?.views ?? [];
|
|
45
|
+
}
|
|
46
|
+
export function rawViewFor(compiled, view) {
|
|
47
|
+
const raw = rawViewsFor(compiled)[view.declarationIndex];
|
|
48
|
+
return raw !== null && typeof raw === 'object' && !Array.isArray(raw)
|
|
49
|
+
? raw
|
|
50
|
+
: undefined;
|
|
51
|
+
}
|
|
52
|
+
export function uniqueViewName(label, views) {
|
|
53
|
+
const used = new Set(views.flatMap((view) => (view !== null && typeof view === 'object' && !Array.isArray(view) && typeof view.name === 'string'
|
|
54
|
+
? [String(view.name).toLocaleLowerCase()]
|
|
55
|
+
: [])));
|
|
56
|
+
let name = label;
|
|
57
|
+
let suffix = 2;
|
|
58
|
+
while (used.has(name.toLocaleLowerCase())) {
|
|
59
|
+
name = `${label} ${suffix}`;
|
|
60
|
+
suffix += 1;
|
|
61
|
+
}
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
64
|
+
export function uniqueViewId(name, views) {
|
|
65
|
+
const used = new Set(views.flatMap((view) => (view !== null && typeof view === 'object' && !Array.isArray(view) && typeof view.id === 'string'
|
|
66
|
+
? [String(view.id)]
|
|
67
|
+
: [])));
|
|
68
|
+
const base = name.toLocaleLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'view';
|
|
69
|
+
let id = base;
|
|
70
|
+
let suffix = 2;
|
|
71
|
+
while (used.has(id)) {
|
|
72
|
+
id = `${base}-${suffix}`;
|
|
73
|
+
suffix += 1;
|
|
74
|
+
}
|
|
75
|
+
return id;
|
|
76
|
+
}
|
|
77
|
+
export function inferredDescriptor(value) {
|
|
78
|
+
if (typeof value === 'boolean')
|
|
79
|
+
return { type: 'checkbox' };
|
|
80
|
+
if (typeof value === 'number')
|
|
81
|
+
return { type: 'number' };
|
|
82
|
+
return { type: 'text' };
|
|
83
|
+
}
|
|
84
|
+
export function jsonPathForValuePath(path) {
|
|
85
|
+
return path.reduce((source, segment) => {
|
|
86
|
+
if (typeof segment === 'number')
|
|
87
|
+
return `${source}[${segment}]`;
|
|
88
|
+
if (/^[A-Za-z_$][\w$]*$/.test(segment))
|
|
89
|
+
return `${source}.${segment}`;
|
|
90
|
+
const escaped = segment
|
|
91
|
+
.replace(/\\/g, '\\\\')
|
|
92
|
+
.replace(/'/g, "\\'")
|
|
93
|
+
.replace(/\n/g, '\\n')
|
|
94
|
+
.replace(/\r/g, '\\r')
|
|
95
|
+
.replace(/\t/g, '\\t');
|
|
96
|
+
return `${source}['${escaped}']`;
|
|
97
|
+
}, '$');
|
|
98
|
+
}
|
|
99
|
+
export function rowHasLongText(compiled, rowPath) {
|
|
100
|
+
const row = getValueAtPath(compiled.root, rowPath);
|
|
101
|
+
return row !== null && typeof row === 'object' && !Array.isArray(row) && Object.entries(row).some(([key, value]) => isLongText(value, schemaForJsonViewPath(compiled.schema, [...rowPath, key])?.descriptor));
|
|
102
|
+
}
|
|
103
|
+
export function titleFieldForRow(root, view, row) {
|
|
104
|
+
const first = view.columns?.[0];
|
|
105
|
+
if (first) {
|
|
106
|
+
const resolved = resolveJsonViewRowPath(root, row, first.path);
|
|
107
|
+
return { label: first.label, path: resolved.sourcePath, value: resolved.value === JSON_VIEW_PATH_MISSING ? undefined : resolved.value };
|
|
108
|
+
}
|
|
109
|
+
const key = inferJsonViewRecordTitleKey([row.value], false);
|
|
110
|
+
if (key)
|
|
111
|
+
return { label: key, path: [...row.sourcePath, key], value: row.value[key] };
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
export function nestedRecordView(root, rowPath, titleKey, name) {
|
|
115
|
+
const row = rowAtSourcePath(root, rowPath);
|
|
116
|
+
if (!row)
|
|
117
|
+
return undefined;
|
|
118
|
+
return {
|
|
119
|
+
row,
|
|
120
|
+
view: {
|
|
121
|
+
id: `nested:${JSON.stringify(rowPath)}`,
|
|
122
|
+
name,
|
|
123
|
+
path: parseJsonViewPath(jsonPathForValuePath(rowPath)),
|
|
124
|
+
sourcePath: rowPath,
|
|
125
|
+
value: row.value,
|
|
126
|
+
display: 'adaptive',
|
|
127
|
+
...(titleKey ? { columns: [{ label: titleKey, path: parseJsonViewPath(jsonPathForValuePath([...rowPath, titleKey])), metadataPath: [] }] } : {}),
|
|
128
|
+
sort: [],
|
|
129
|
+
declarationIndex: -1,
|
|
130
|
+
metadataPath: [],
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function titleForRow(root, view, row) {
|
|
135
|
+
const field = titleFieldForRow(root, view, row);
|
|
136
|
+
if (field && field.value != null && String(field.value).trim())
|
|
137
|
+
return String(field.value);
|
|
138
|
+
if (field)
|
|
139
|
+
return 'Untitled';
|
|
140
|
+
return String(row.key);
|
|
141
|
+
}
|
|
142
|
+
export function rowAtSourcePath(root, sourcePath) {
|
|
143
|
+
if (!sourcePath)
|
|
144
|
+
return undefined;
|
|
145
|
+
const value = getValueAtPath(root, sourcePath);
|
|
146
|
+
if (value === VALUE_PATH_MISSING || value === null || typeof value !== 'object' || Array.isArray(value))
|
|
147
|
+
return undefined;
|
|
148
|
+
const key = sourcePath[sourcePath.length - 1] ?? '$';
|
|
149
|
+
return { key, value: value, sourcePath };
|
|
150
|
+
}
|
|
151
|
+
export function rowForPage(view) {
|
|
152
|
+
if (view.value === null || typeof view.value !== 'object' || Array.isArray(view.value))
|
|
153
|
+
return undefined;
|
|
154
|
+
return {
|
|
155
|
+
key: view.sourcePath[view.sourcePath.length - 1] ?? view.name,
|
|
156
|
+
value: view.value,
|
|
157
|
+
sourcePath: view.sourcePath,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function pathLabel(path) {
|
|
161
|
+
return `$${path.map((segment) => (typeof segment === 'number'
|
|
162
|
+
? `[${segment}]`
|
|
163
|
+
: /^[A-Za-z_$][\w$]*$/.test(segment)
|
|
164
|
+
? `.${segment}`
|
|
165
|
+
: `[${JSON.stringify(segment)}]`)).join('')}`;
|
|
166
|
+
}
|
|
167
|
+
export function sameJsonViewPath(left, right) {
|
|
168
|
+
if (!right || left.root !== right.root || left.segments.length !== right.segments.length)
|
|
169
|
+
return false;
|
|
170
|
+
return left.segments.every((segment, index) => {
|
|
171
|
+
const candidate = right.segments[index];
|
|
172
|
+
if (segment.kind !== candidate?.kind)
|
|
173
|
+
return false;
|
|
174
|
+
if (segment.kind === 'property')
|
|
175
|
+
return segment.key === candidate.key;
|
|
176
|
+
if (segment.kind === 'index')
|
|
177
|
+
return segment.index === candidate.index;
|
|
178
|
+
return true;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The viewer owns annotation persistence; the host reports data-save progress. */
|
|
2
|
+
export declare function ViewSaveStatus({ embedded, objectRoot, saving, dirty, error, invalidFormat, invalidSourceError, onSave, conversionConfirmation }: {
|
|
3
|
+
conversionConfirmation?: 'viewer' | 'host';
|
|
4
|
+
embedded: boolean;
|
|
5
|
+
objectRoot: boolean;
|
|
6
|
+
saving: boolean;
|
|
7
|
+
dirty: boolean;
|
|
8
|
+
invalidSourceError?: string;
|
|
9
|
+
invalidFormat?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
onSave?: () => Promise<void>;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { AlertCircle, Check, Loader2 } from 'lucide-react';
|
|
4
|
+
import { ConditionalTooltip } from '../primitives/tooltip.js';
|
|
5
|
+
import { Dialog, DialogContent, DialogTitle, DialogDescription, DialogFooter } from '../primitives/dialog.js';
|
|
6
|
+
/** The viewer owns annotation persistence; the host reports data-save progress. */
|
|
7
|
+
export function ViewSaveStatus({ embedded, objectRoot, saving, dirty, error, invalidFormat, invalidSourceError, onSave, conversionConfirmation = 'viewer' }) {
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
10
|
+
const [failure, setFailure] = useState();
|
|
11
|
+
const [pending, setPending] = useState(false);
|
|
12
|
+
const problem = error || failure;
|
|
13
|
+
const busy = saving || pending;
|
|
14
|
+
const label = problem ? 'Changes are not saved.' : busy ? 'Saving…'
|
|
15
|
+
: invalidFormat ? dirty ? `Invalid ${invalidFormat}. Changes are not saved.` : `Data is saved, but this is not valid ${invalidFormat}.`
|
|
16
|
+
: dirty ? 'Changes are not saved.'
|
|
17
|
+
: embedded ? 'Data and views are saved.' : 'Data is saved, views are not. Click to save.';
|
|
18
|
+
const details = [invalidSourceError, problem].filter(Boolean).join("\n\n") || (invalidFormat ? `The source is not valid ${invalidFormat}.` : undefined);
|
|
19
|
+
const tooltip = details ? `${label} Click for details.` : label;
|
|
20
|
+
const actionable = !embedded && Boolean(onSave) && !busy && !dirty && !error && !invalidFormat;
|
|
21
|
+
const save = () => {
|
|
22
|
+
setOpen(false);
|
|
23
|
+
setFailure(undefined);
|
|
24
|
+
setPending(true);
|
|
25
|
+
void onSave?.().catch((cause) => setFailure(cause instanceof Error ? cause.message : 'Could not save views')).finally(() => setPending(false));
|
|
26
|
+
};
|
|
27
|
+
const icon = problem ? _jsx(AlertCircle, { className: "h-3.5 w-3.5" }) : busy ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : _jsx(Check, { className: "h-3.5 w-3.5" });
|
|
28
|
+
const className = `grid h-7 w-7 shrink-0 place-items-center rounded-md ${invalidFormat || problem ? 'text-red-400' : problem || dirty || !embedded ? 'text-orange-400' : 'text-green-600'}`;
|
|
29
|
+
return _jsxs(_Fragment, { children: [_jsx(ConditionalTooltip, { enabled: !open && !detailsOpen, label: tooltip, children: details ? _jsx("button", { type: "button", "aria-label": tooltip, className: `${className} hover:bg-accent`, onClick: () => setDetailsOpen(true), children: icon }) : actionable ? _jsx("button", { type: "button", "aria-label": label, className: `${className} hover:bg-accent`, onClick: () => { if (!objectRoot && conversionConfirmation === 'host')
|
|
30
|
+
save();
|
|
31
|
+
else {
|
|
32
|
+
setFailure(undefined);
|
|
33
|
+
setOpen(true);
|
|
34
|
+
} }, children: icon })
|
|
35
|
+
: _jsx("span", { role: "img", tabIndex: 0, "aria-label": label, className: className, children: icon }) }), _jsx(Dialog, { open: detailsOpen, onOpenChange: setDetailsOpen, children: _jsxs(DialogContent, { className: "max-w-sm", children: [_jsx(DialogTitle, { children: invalidFormat ? `Invalid ${invalidFormat}` : 'Save error' }), _jsx(DialogDescription, { className: "max-h-64 overflow-auto whitespace-pre-wrap break-words", children: details }), _jsx(DialogFooter, { children: _jsx("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-sm", onClick: () => setDetailsOpen(false), children: "Close" }) })] }) }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-w-sm", children: [_jsx(DialogTitle, { children: "Save views to JSON?" }), _jsx(DialogDescription, { children: objectRoot
|
|
36
|
+
? 'Adds a $jsonviews property to this JSON. Future view changes will save there automatically.'
|
|
37
|
+
: 'Saving views requires object-root JSON. Your data will move under a data property, alongside $jsonviews. Code that reads this document must use the data property after conversion.' }), _jsxs(DialogFooter, { children: [_jsx("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-sm", onClick: () => setOpen(false), children: "Cancel" }), _jsx("button", { type: "button", className: "rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground", onClick: save, children: objectRoot ? 'Save views' : 'Convert to object-root JSON' })] })] }) })] });
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type CompiledJsonViewSchema, type CompiledJsonViewView } from '@script-it/json-views-core';
|
|
2
|
+
export interface JsonViewViewPathOption {
|
|
3
|
+
label: string;
|
|
4
|
+
path: string;
|
|
5
|
+
recordCollection: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface JsonViewNewViewDraft extends JsonViewViewPathOption {
|
|
8
|
+
display: 'table' | 'kanban';
|
|
9
|
+
groupBy?: string;
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
declare function BuiltInViewSettings({ disabled, hasUnsavedChanges, onDelete, onDuplicate, onPreview, onSave, rawView, saveMode, saveUnavailableReason, schema, unavailableReason, view, }: {
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
hasUnsavedChanges?: boolean;
|
|
15
|
+
onDelete?: () => Promise<void>;
|
|
16
|
+
onDuplicate?: () => Promise<void>;
|
|
17
|
+
onPreview?: (value: Record<string, unknown> | undefined) => void;
|
|
18
|
+
onSave: (value: Record<string, unknown>) => Promise<void>;
|
|
19
|
+
rawView: Record<string, unknown>;
|
|
20
|
+
saveMode?: 'automatic' | 'explicit';
|
|
21
|
+
saveUnavailableReason?: string;
|
|
22
|
+
schema: readonly CompiledJsonViewSchema[];
|
|
23
|
+
unavailableReason?: string;
|
|
24
|
+
view: CompiledJsonViewView;
|
|
25
|
+
}): import("react").JSX.Element;
|
|
26
|
+
export declare function JsonViewAddView({ align, currentPath, disabled, onAdd, root, schema, unavailableReason: unavailableReasonOverride, }: {
|
|
27
|
+
align?: 'start' | 'end';
|
|
28
|
+
currentPath?: string;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
onAdd: (option: JsonViewNewViewDraft) => Promise<void>;
|
|
31
|
+
root: unknown;
|
|
32
|
+
schema: readonly CompiledJsonViewSchema[];
|
|
33
|
+
unavailableReason?: string;
|
|
34
|
+
}): import("react").JSX.Element;
|
|
35
|
+
export declare function JsonViewViewSettings(props: Parameters<typeof BuiltInViewSettings>[0]): import("react").JSX.Element;
|
|
36
|
+
export {};
|