@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,113 @@
|
|
|
1
|
+
.json-views-app {
|
|
2
|
+
--background: var(--json-views-background, oklch(0.988 0.003 280));
|
|
3
|
+
--foreground: var(--json-views-foreground, oklch(0.27 0.018 280));
|
|
4
|
+
--card: var(--json-views-card, oklch(1 0 0));
|
|
5
|
+
--card-foreground: var(--json-views-card-foreground, oklch(0.27 0.018 280));
|
|
6
|
+
--muted: var(--json-views-muted, oklch(0.957 0.008 280));
|
|
7
|
+
--muted-foreground: var(--json-views-muted-foreground, oklch(0.49 0.022 280));
|
|
8
|
+
--accent: var(--json-views-accent, oklch(0.94 0.019 285));
|
|
9
|
+
--accent-foreground: var(--json-views-accent-foreground, oklch(0.36 0.065 285));
|
|
10
|
+
--primary: var(--json-views-primary, oklch(0.49 0.15 285));
|
|
11
|
+
--primary-foreground: var(--json-views-primary-foreground, oklch(0.99 0.003 285));
|
|
12
|
+
--border: var(--json-views-border, oklch(0.901 0.009 280));
|
|
13
|
+
--input: var(--json-views-input, oklch(0.901 0.009 280));
|
|
14
|
+
--ring: var(--json-views-ring, oklch(0.55 0.15 285));
|
|
15
|
+
--link: var(--json-views-link, oklch(0.48 0.14 250));
|
|
16
|
+
--warning: var(--json-views-warning, oklch(0.48 0.09 70));
|
|
17
|
+
--destructive: var(--json-views-destructive, oklch(0.49 0.16 25));
|
|
18
|
+
--destructive-border: var(--json-views-destructive-border, oklch(0.86 0.055 25));
|
|
19
|
+
--destructive-surface: var(--json-views-destructive-surface, oklch(0.965 0.015 25));
|
|
20
|
+
--destructive-foreground: var(--json-views-destructive-foreground, oklch(0.43 0.12 25));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.json-views-app[data-json-views-theme="dark"] {
|
|
24
|
+
--background: var(--json-views-background, oklch(0.205 0.012 280));
|
|
25
|
+
--foreground: var(--json-views-foreground, oklch(0.93 0.01 280));
|
|
26
|
+
--card: var(--json-views-card, oklch(0.245 0.015 280));
|
|
27
|
+
--card-foreground: var(--json-views-card-foreground, oklch(0.93 0.01 280));
|
|
28
|
+
--muted: var(--json-views-muted, oklch(0.27 0.014 280));
|
|
29
|
+
--muted-foreground: var(--json-views-muted-foreground, oklch(0.71 0.025 280));
|
|
30
|
+
--accent: var(--json-views-accent, oklch(0.31 0.035 285));
|
|
31
|
+
--accent-foreground: var(--json-views-accent-foreground, oklch(0.87 0.035 285));
|
|
32
|
+
--primary: var(--json-views-primary, oklch(0.79 0.105 285));
|
|
33
|
+
--primary-foreground: var(--json-views-primary-foreground, oklch(0.23 0.04 285));
|
|
34
|
+
--border: var(--json-views-border, oklch(0.355 0.019 280));
|
|
35
|
+
--input: var(--json-views-input, oklch(0.355 0.019 280));
|
|
36
|
+
--ring: var(--json-views-ring, oklch(0.73 0.12 285));
|
|
37
|
+
--link: var(--json-views-link, oklch(0.77 0.1 250));
|
|
38
|
+
--warning: var(--json-views-warning, oklch(0.81 0.11 80));
|
|
39
|
+
--destructive: var(--json-views-destructive, oklch(0.77 0.13 25));
|
|
40
|
+
--destructive-border: var(--json-views-destructive-border, oklch(0.46 0.08 25));
|
|
41
|
+
--destructive-surface: var(--json-views-destructive-surface, oklch(0.28 0.03 25));
|
|
42
|
+
--destructive-foreground: var(--json-views-destructive-foreground, oklch(0.86 0.07 25));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.json-views-app .json-views-inline-feedback-action {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
width: auto;
|
|
48
|
+
min-width: 24px;
|
|
49
|
+
max-width: 24px;
|
|
50
|
+
height: 24px;
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
gap: 0;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
padding: 0;
|
|
57
|
+
border: 0;
|
|
58
|
+
border-radius: 5px;
|
|
59
|
+
color: var(--muted-foreground);
|
|
60
|
+
background: transparent;
|
|
61
|
+
font-family: inherit;
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
font-weight: 400;
|
|
64
|
+
line-height: 1;
|
|
65
|
+
letter-spacing: normal;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
transition: max-width 220ms cubic-bezier(.2, .8, .2, 1), padding 220ms ease, gap 220ms ease, color 150ms ease, background-color 150ms ease;
|
|
69
|
+
}
|
|
70
|
+
.json-views-app .json-views-inline-feedback-action:hover,
|
|
71
|
+
.json-views-app .json-views-inline-feedback-action.is-feedback {
|
|
72
|
+
color: var(--foreground);
|
|
73
|
+
background: var(--accent);
|
|
74
|
+
}
|
|
75
|
+
.json-views-app .json-views-inline-feedback-action.is-strong { color: var(--foreground); }
|
|
76
|
+
.json-views-app .json-views-inline-feedback-action.is-destructive { color: var(--destructive); }
|
|
77
|
+
.json-views-app .json-views-inline-feedback-action.is-feedback {
|
|
78
|
+
max-width: 150px;
|
|
79
|
+
gap: 5px;
|
|
80
|
+
padding-inline: 7px;
|
|
81
|
+
}
|
|
82
|
+
.json-views-app .json-views-inline-feedback-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
|
|
83
|
+
.json-views-app .json-views-inline-feedback-action:disabled { cursor: default; opacity: .5; }
|
|
84
|
+
.json-views-app .json-views-inline-feedback-action > svg { flex-shrink: 0; }
|
|
85
|
+
.json-views-app .json-views-inline-feedback-action.is-feedback > svg { animation: json-views-action-confirm 220ms cubic-bezier(.2, .8, .2, 1); }
|
|
86
|
+
.json-views-app .json-views-inline-feedback-label {
|
|
87
|
+
max-width: 0;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
opacity: 0;
|
|
90
|
+
transform: translateX(-4px);
|
|
91
|
+
transition: max-width 220ms cubic-bezier(.2, .8, .2, 1), opacity 150ms ease 50ms, transform 220ms ease;
|
|
92
|
+
}
|
|
93
|
+
.json-views-app .json-views-inline-feedback-action.is-feedback .json-views-inline-feedback-label {
|
|
94
|
+
max-width: 120px;
|
|
95
|
+
opacity: 1;
|
|
96
|
+
transform: translateX(0);
|
|
97
|
+
}
|
|
98
|
+
@keyframes json-views-action-confirm {
|
|
99
|
+
from { opacity: .4; transform: scale(.72); }
|
|
100
|
+
to { opacity: 1; transform: scale(1); }
|
|
101
|
+
}
|
|
102
|
+
@media (prefers-reduced-motion: reduce) {
|
|
103
|
+
.json-views-app .json-views-inline-feedback-action,
|
|
104
|
+
.json-views-app .json-views-inline-feedback-label { transition: none; }
|
|
105
|
+
.json-views-app .json-views-inline-feedback-action.is-feedback > svg { animation: none; }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
.json-views-app {
|
|
110
|
+
color: var(--foreground);
|
|
111
|
+
background: var(--background);
|
|
112
|
+
font-family: var(--json-views-font-family, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
|
|
113
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { AtomicValue } from './atomic-value.js';
|
|
3
|
+
interface AtomicValueEditorProps {
|
|
4
|
+
actions?: ReactNode;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
editorClassName?: string;
|
|
8
|
+
label: string;
|
|
9
|
+
multiline?: boolean;
|
|
10
|
+
onCommit: (value: AtomicValue) => Promise<void>;
|
|
11
|
+
preserveLayout?: boolean;
|
|
12
|
+
saving?: boolean;
|
|
13
|
+
value: AtomicValue;
|
|
14
|
+
}
|
|
15
|
+
export declare function EditValueAction({ disabled, label, onClick, }: {
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
label: string;
|
|
18
|
+
onClick: () => void;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
export declare function AtomicValueEditor({ actions, children, className, editorClassName, label, multiline, onCommit, preserveLayout, saving, value, }: AtomicValueEditorProps): import("react").JSX.Element;
|
|
21
|
+
export declare function StructuredValueCellFrame({ activationDisabled, activationLabel, activationRole, actions, children, leading, className, onActivate, overlayActions, }: {
|
|
22
|
+
activationDisabled?: boolean;
|
|
23
|
+
activationLabel?: string;
|
|
24
|
+
/** Use a group when rendered content contains its own interactive descendants. */
|
|
25
|
+
activationRole?: 'button' | 'group';
|
|
26
|
+
actions?: ReactNode;
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
leading?: ReactNode;
|
|
29
|
+
className?: string;
|
|
30
|
+
onActivate?: () => void;
|
|
31
|
+
overlayActions?: boolean;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, useEffect, useLayoutEffect, useRef, useState, } from 'react';
|
|
3
|
+
import { Pencil } from 'lucide-react';
|
|
4
|
+
import { cn } from '../lib/cn.js';
|
|
5
|
+
import { StructuredCellFillContext } from './structured-cell-fill-context.js';
|
|
6
|
+
import { useEditBaseChanged } from './edit-base.js';
|
|
7
|
+
const CONTROL_CLASS = 'min-h-8 w-full min-w-0 rounded-md border border-border bg-background px-2 [font:inherit] text-foreground outline-none transition-colors placeholder:text-muted-foreground hover:border-input focus:border-ring focus:ring-2 focus:ring-inset focus:ring-ring/20';
|
|
8
|
+
export function EditValueAction({ disabled = false, label, onClick, }) {
|
|
9
|
+
return (_jsx("button", { type: "button", "data-id": "structured-edit-value", "aria-label": `Edit ${label}`, disabled: disabled, className: "grid h-6 w-6 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40", onClick: (event) => {
|
|
10
|
+
event.stopPropagation();
|
|
11
|
+
onClick();
|
|
12
|
+
}, children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }));
|
|
13
|
+
}
|
|
14
|
+
export function AtomicValueEditor({ actions, children, className, editorClassName, label, multiline = false, onCommit, preserveLayout = false, saving = false, value, }) {
|
|
15
|
+
const [editing, setEditing] = useState(false);
|
|
16
|
+
const fillCell = useContext(StructuredCellFillContext);
|
|
17
|
+
const frameRef = useRef(null);
|
|
18
|
+
const [editorMinHeight, setEditorMinHeight] = useState();
|
|
19
|
+
const stableLayout = multiline || preserveLayout;
|
|
20
|
+
const preserveHeight = () => {
|
|
21
|
+
if (!stableLayout)
|
|
22
|
+
return;
|
|
23
|
+
const height = frameRef.current?.getBoundingClientRect().height ?? 0;
|
|
24
|
+
if (height > 0)
|
|
25
|
+
setEditorMinHeight(height);
|
|
26
|
+
};
|
|
27
|
+
const stopEditing = () => {
|
|
28
|
+
setEditorMinHeight(undefined);
|
|
29
|
+
setEditing(false);
|
|
30
|
+
};
|
|
31
|
+
if (editing) {
|
|
32
|
+
const editor = (_jsx(AtomicValueDirectEditor, { controlClassName: editorClassName, label: label, disabled: saving, minHeight: editorMinHeight, multiline: multiline, onCancel: stopEditing, onCommit: async (nextValue) => {
|
|
33
|
+
await onCommit(nextValue);
|
|
34
|
+
stopEditing();
|
|
35
|
+
}, value: value }));
|
|
36
|
+
// A zero-padding host cell would press the input against the cell
|
|
37
|
+
// borders. Deliberately tighter than the idle frame's px-3/py-2: the
|
|
38
|
+
// 32px control plus py-1.5 matches the 44px row exactly (py-2 would grow
|
|
39
|
+
// the row on every edit toggle), and the control's own ~8px text inset
|
|
40
|
+
// on top of px-1.5 lands the edited text near the idle text position.
|
|
41
|
+
const paddedEditor = fillCell ? _jsx("div", { className: "px-1.5 py-1.5", children: editor }) : editor;
|
|
42
|
+
return stableLayout
|
|
43
|
+
? _jsx("div", { ref: frameRef, "data-id": "atomic-value-editor-frame", className: "h-full min-w-0", style: editorMinHeight ? { minHeight: editorMinHeight } : undefined, children: paddedEditor })
|
|
44
|
+
: paddedEditor;
|
|
45
|
+
}
|
|
46
|
+
const display = (_jsx(StructuredValueCellFrame, { actions: actions, activationDisabled: saving, activationLabel: `Edit ${label}`, className: className, onActivate: () => {
|
|
47
|
+
preserveHeight();
|
|
48
|
+
setEditing(true);
|
|
49
|
+
}, children: children }));
|
|
50
|
+
return stableLayout
|
|
51
|
+
? _jsx("div", { ref: frameRef, "data-id": "atomic-value-editor-frame", className: "h-full min-w-0", style: editorMinHeight ? { minHeight: editorMinHeight } : undefined, children: display })
|
|
52
|
+
: display;
|
|
53
|
+
}
|
|
54
|
+
export function StructuredValueCellFrame({ activationDisabled = false, activationLabel, activationRole = 'button', actions, children, leading, className, onActivate, overlayActions = false, }) {
|
|
55
|
+
const fillCell = useContext(StructuredCellFillContext);
|
|
56
|
+
// An editable cell advertises itself with a primary-tinted hover — the
|
|
57
|
+
// neutral accent tone sits on white/card surfaces where it is barely
|
|
58
|
+
// perceptible. A low alpha reads on white but is swallowed by the
|
|
59
|
+
// near-black dark surfaces, so dark raises it.
|
|
60
|
+
return (_jsxs("div", { "data-id": "structured-value-cell", className: cn('group/structured-cell json-viewer-hovercell relative flex min-h-7 w-full min-w-0 items-start transition-colors',
|
|
61
|
+
// Inside a zero-padding table cell the frame IS the cell face:
|
|
62
|
+
// square corners, and the padding the cell would otherwise carry.
|
|
63
|
+
// Table values use the entire width up to the column divider. The
|
|
64
|
+
// right edge is deliberately flush so clipped content makes the
|
|
65
|
+
// boundary of its column unmistakable.
|
|
66
|
+
fillCell ? 'h-full items-center rounded-none py-2 pl-3 pr-0' : 'rounded-md px-1.5', onActivate && !activationDisabled
|
|
67
|
+
? fillCell
|
|
68
|
+
? 'before:absolute before:inset-y-0 before:-left-5 before:right-0 hover:before:bg-primary/10 dark:hover:before:bg-primary/25'
|
|
69
|
+
: 'hover:bg-primary/10 dark:hover:bg-primary/25'
|
|
70
|
+
: fillCell
|
|
71
|
+
? 'before:absolute before:inset-y-0 before:-left-5 before:right-0 hover:before:bg-accent/70'
|
|
72
|
+
: 'hover:bg-accent/70', fillCell && onActivate && !activationDisabled && 'cursor-text', className), onClick: (event) => {
|
|
73
|
+
// The table cell's padding belongs to the same action as its value.
|
|
74
|
+
// Child actions keep their own handlers and must not activate twice.
|
|
75
|
+
if (fillCell && event.target === event.currentTarget && !activationDisabled)
|
|
76
|
+
onActivate?.();
|
|
77
|
+
}, children: [leading && _jsx("span", { className: "mr-1.5 flex min-h-7 shrink-0 items-center", children: leading }), _jsx("div", { "data-id": onActivate ? 'atomic-edit-value' : undefined, "aria-disabled": onActivate ? activationDisabled : undefined, "aria-label": onActivate ? activationLabel : undefined, role: onActivate ? activationRole : undefined, tabIndex: onActivate && !activationDisabled ? 0 : undefined, title: onActivate ? activationLabel : undefined, className: cn('flex min-h-7 min-w-0 flex-1 items-center', fillCell && 'self-stretch',
|
|
78
|
+
// The actions overlay the trailing edge, so the value ends before
|
|
79
|
+
// their lane: truncation stops short of the chip rather than being
|
|
80
|
+
// covered by it. Reserved unconditionally — sizing it on hover
|
|
81
|
+
// would re-truncate the text under the pointer.
|
|
82
|
+
actions && !fillCell && !overlayActions && 'pr-6', onActivate && !activationDisabled && 'cursor-text focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring'), onClick: (event) => {
|
|
83
|
+
if (!onActivate || activationDisabled)
|
|
84
|
+
return;
|
|
85
|
+
const interactiveTarget = event.target instanceof Element
|
|
86
|
+
? event.target.closest('a[href], button, input, textarea, select, summary, [controls], [contenteditable]:not([contenteditable="false"]), [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="slider"], [role="tab"], [role="menuitem"], [role="option"]')
|
|
87
|
+
: null;
|
|
88
|
+
if (interactiveTarget && interactiveTarget !== event.currentTarget)
|
|
89
|
+
return;
|
|
90
|
+
onActivate();
|
|
91
|
+
}, onKeyDown: (event) => {
|
|
92
|
+
if (!onActivate || activationDisabled)
|
|
93
|
+
return;
|
|
94
|
+
if (event.target !== event.currentTarget)
|
|
95
|
+
return;
|
|
96
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
97
|
+
return;
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
onActivate();
|
|
100
|
+
}, children: children }), actions && (
|
|
101
|
+
// No `z-index`: a rank here escapes the cell and bids against
|
|
102
|
+
// whatever else the surface stacks — a table's sticky header sits in
|
|
103
|
+
// a different subtree, so an equal rank hands the win to this one on
|
|
104
|
+
// DOM order alone. On touch there is no `hover:hover` to fall back
|
|
105
|
+
// on, so these are visible on every row at once, including rows
|
|
106
|
+
// scrolled under a sticky header.
|
|
107
|
+
_jsx("div", { "data-id": "structured-value-cell-actions", className: cn('absolute right-1 flex opacity-100 transition-opacity [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover/structured-cell:opacity-100 [@media(hover:hover)]:group-focus-within/structured-cell:opacity-100',
|
|
108
|
+
// No surface of its own: the layer is exactly the chip, so it
|
|
109
|
+
// never has to guess the colour of the cell face it sits on —
|
|
110
|
+
// which the row wash and the frame's own hover both repaint.
|
|
111
|
+
// A table row is a uniform height, so the chip centres in it;
|
|
112
|
+
// a free-standing cell can be a tall block of prose, where the
|
|
113
|
+
// chip belongs beside its first line.
|
|
114
|
+
overlayActions ? 'top-2 right-2 rounded-md bg-background/90 shadow-sm' : fillCell ? 'top-1/2 -translate-y-1/2' : 'top-0.5'), children: actions }))] }));
|
|
115
|
+
}
|
|
116
|
+
function AtomicValueDirectEditor({ controlClassName, disabled = false, label, minHeight, multiline, onCancel, onCommit, value, }) {
|
|
117
|
+
const [draft, setDraft] = useState(typeof value === 'boolean' ? value : String(value));
|
|
118
|
+
const [error, setError] = useState();
|
|
119
|
+
const containerRef = useRef(null);
|
|
120
|
+
const textareaRef = useRef(null);
|
|
121
|
+
const committingRef = useRef(false);
|
|
122
|
+
const originalValueRef = useRef(value);
|
|
123
|
+
const attemptedCommitRef = useRef(false);
|
|
124
|
+
const baseChanged = useEditBaseChanged();
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
containerRef.current?.querySelector('input, textarea')?.focus({ preventScroll: multiline });
|
|
127
|
+
}, [multiline]);
|
|
128
|
+
useLayoutEffect(() => {
|
|
129
|
+
if (!multiline || !textareaRef.current)
|
|
130
|
+
return;
|
|
131
|
+
const textarea = textareaRef.current;
|
|
132
|
+
textarea.style.height = 'auto';
|
|
133
|
+
textarea.style.height = `${Math.max(minHeight ?? 0, textarea.scrollHeight)}px`;
|
|
134
|
+
}, [draft, minHeight, multiline]);
|
|
135
|
+
const commit = async (nextBoolean) => {
|
|
136
|
+
if (committingRef.current)
|
|
137
|
+
return;
|
|
138
|
+
if (baseChanged || (!attemptedCommitRef.current && !Object.is(value, originalValueRef.current))) {
|
|
139
|
+
setError('This document changed outside this editor. Cancel and reopen it before saving.');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let nextValue;
|
|
143
|
+
if (typeof value === 'boolean') {
|
|
144
|
+
nextValue = nextBoolean ?? Boolean(draft);
|
|
145
|
+
}
|
|
146
|
+
else if (typeof value === 'number') {
|
|
147
|
+
const parsed = Number(draft);
|
|
148
|
+
if (typeof draft !== 'string' || draft.trim() === '' || !Number.isFinite(parsed)) {
|
|
149
|
+
setError('Enter a valid number');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
nextValue = parsed;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
nextValue = String(draft);
|
|
156
|
+
}
|
|
157
|
+
if (Object.is(nextValue, value) && !error) {
|
|
158
|
+
onCancel();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
committingRef.current = true;
|
|
162
|
+
attemptedCommitRef.current = true;
|
|
163
|
+
setError(undefined);
|
|
164
|
+
try {
|
|
165
|
+
await onCommit(nextValue);
|
|
166
|
+
}
|
|
167
|
+
catch (commitError) {
|
|
168
|
+
setError(commitError instanceof Error ? commitError.message : 'Could not save this value');
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
committingRef.current = false;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
return (_jsxs("div", { ref: containerRef, "data-id": "atomic-direct-editor", className: "relative min-w-0", onBlur: (event) => {
|
|
175
|
+
if (event.currentTarget.contains(event.relatedTarget))
|
|
176
|
+
return;
|
|
177
|
+
void commit();
|
|
178
|
+
}, onKeyDown: (event) => {
|
|
179
|
+
if (event.key === 'Escape') {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
event.stopPropagation();
|
|
182
|
+
onCancel();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const isTextarea = event.target instanceof HTMLTextAreaElement;
|
|
186
|
+
if (event.key === 'Enter' && (!isTextarea || event.metaKey || event.ctrlKey)) {
|
|
187
|
+
event.preventDefault();
|
|
188
|
+
void commit();
|
|
189
|
+
}
|
|
190
|
+
}, children: [typeof value === 'boolean' ? (_jsxs("label", { className: cn('flex min-h-8 items-center gap-2 rounded-md border border-border bg-background px-2 text-xs text-foreground', controlClassName), children: [_jsx("input", { "aria-label": `Edit ${label}`, type: "checkbox", disabled: disabled, checked: draft === true, className: "h-4 w-4 accent-primary", onChange: (event) => {
|
|
191
|
+
const next = event.target.checked;
|
|
192
|
+
setDraft(next);
|
|
193
|
+
void commit(next);
|
|
194
|
+
} }), _jsx("span", { children: draft === true ? 'True' : 'False' })] })) : multiline ? (_jsx("textarea", { ref: textareaRef, "aria-label": `Edit ${label}`, disabled: disabled, className: cn(CONTROL_CLASS, 'min-h-24 resize-y py-2', controlClassName), style: minHeight ? { minHeight } : undefined, value: draft, onChange: (event) => setDraft(event.target.value) })) : (_jsx("input", { "aria-label": `Edit ${label}`, type: "text", disabled: disabled, inputMode: typeof value === 'number' ? 'decimal' : undefined, className: cn(CONTROL_CLASS, controlClassName), value: draft, onChange: (event) => setDraft(event.target.value) })), error && _jsx("p", { role: "alert", className: "mt-1 text-[0.6875rem] text-destructive", children: error })] }));
|
|
195
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ColumnFilterOperatorOption, ColumnFilterRule, SortConfig, SortDirection, TabularObjectArrayViewProps } from './table-types.js';
|
|
2
|
+
export declare function AddColumnHeader({ onAdd }: {
|
|
3
|
+
onAdd: NonNullable<TabularObjectArrayViewProps['onAddColumn']>;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
|
+
export declare function ColumnHeaderMenu({ activeFilter, activeSort, canHide, column, draggable, dragging, filterOperators, filterValues, label, onFilter, onHide, onMoveFirst, onDelete, onRename, onSort, }: {
|
|
6
|
+
activeFilter?: ColumnFilterRule;
|
|
7
|
+
activeSort: SortConfig;
|
|
8
|
+
canHide: boolean;
|
|
9
|
+
column: string;
|
|
10
|
+
draggable?: boolean;
|
|
11
|
+
dragging?: boolean;
|
|
12
|
+
filterOperators?: readonly ColumnFilterOperatorOption[];
|
|
13
|
+
filterValues?: readonly unknown[];
|
|
14
|
+
label: string;
|
|
15
|
+
onFilter?: (column: string, filter: ColumnFilterRule | null) => void | Promise<void>;
|
|
16
|
+
onMoveFirst?: () => void | Promise<void>;
|
|
17
|
+
onHide?: (column: string) => void | Promise<void>;
|
|
18
|
+
onDelete?: (column: string) => void | Promise<void>;
|
|
19
|
+
onRename?: (column: string, label: string) => void | Promise<void>;
|
|
20
|
+
onSort?: (column: string, direction: SortDirection | null) => void | Promise<void>;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { jsonValueText } from '@script-it/json-views-core';
|
|
3
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { ArrowLeftToLine, ArrowUpDown, ChevronDown, ChevronRight, ChevronUp, EyeOff, Filter, Plus, Trash2, X } from 'lucide-react';
|
|
6
|
+
import { useDismiss } from '../lib/use-dismiss.js';
|
|
7
|
+
import { useAnchoredPosition } from '../lib/use-anchored-position.js';
|
|
8
|
+
import { MenuSelect } from '../primitives/menu-select.js';
|
|
9
|
+
import { useJsonViewsPortalContainer } from '../surface.js';
|
|
10
|
+
const DEFAULT_FILTER_OPERATORS = [
|
|
11
|
+
{ value: 'eq', label: 'is' },
|
|
12
|
+
{ value: 'neq', label: 'is not' },
|
|
13
|
+
{ value: 'in', label: 'is any of' },
|
|
14
|
+
{ value: 'notIn', label: 'is none of' },
|
|
15
|
+
{ value: 'gt', label: 'is greater than' },
|
|
16
|
+
{ value: 'gte', label: 'is at least' },
|
|
17
|
+
{ value: 'lt', label: 'is less than' },
|
|
18
|
+
{ value: 'lte', label: 'is at most' },
|
|
19
|
+
{ value: 'contains', label: 'contains' },
|
|
20
|
+
{ value: 'notContains', label: 'does not contain' },
|
|
21
|
+
{ value: 'isEmpty', label: 'is empty' },
|
|
22
|
+
{ value: 'isNotEmpty', label: 'is not empty' },
|
|
23
|
+
];
|
|
24
|
+
function filterValueText(value, operator) {
|
|
25
|
+
if ((operator === 'in' || operator === 'notIn') && Array.isArray(value))
|
|
26
|
+
return value.map(jsonValueText).join(', ');
|
|
27
|
+
if (typeof value === 'string')
|
|
28
|
+
return value;
|
|
29
|
+
if (value === undefined)
|
|
30
|
+
return '';
|
|
31
|
+
try {
|
|
32
|
+
return JSON.stringify(value);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return jsonValueText(value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function AddColumnHeader({ onAdd }) {
|
|
39
|
+
const portalContainer = useJsonViewsPortalContainer();
|
|
40
|
+
const [open, setOpen] = useState(false);
|
|
41
|
+
const [label, setLabel] = useState('');
|
|
42
|
+
const [type, setType] = useState('text');
|
|
43
|
+
const [saving, setSaving] = useState(false);
|
|
44
|
+
const [error, setError] = useState();
|
|
45
|
+
const triggerRef = useRef(null);
|
|
46
|
+
const dialogRef = useRef(null);
|
|
47
|
+
const position = useAnchoredPosition(open, triggerRef, dialogRef);
|
|
48
|
+
const close = () => { if (!saving) {
|
|
49
|
+
setOpen(false);
|
|
50
|
+
setError(undefined);
|
|
51
|
+
} };
|
|
52
|
+
const show = () => {
|
|
53
|
+
setOpen(true);
|
|
54
|
+
};
|
|
55
|
+
useDismiss({ enabled: open, onDismiss: close, insideRefs: [triggerRef, dialogRef] });
|
|
56
|
+
return _jsxs(_Fragment, { children: [_jsxs("button", { ref: triggerRef, type: "button", "data-id": "tabular-add-column", className: "flex h-full min-h-9 w-full items-center gap-1.5 px-3 py-2 text-left text-muted-foreground hover:bg-accent hover:text-foreground", onClick: show, children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Add property"] }), open && typeof document !== 'undefined' && createPortal(_jsxs("form", { ref: dialogRef, "aria-label": "Add property", className: "z-[100] grid w-64 gap-3 rounded-md border border-border bg-card p-3 text-foreground shadow-lg", style: position, onSubmit: (event) => {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
const nextLabel = label.trim();
|
|
59
|
+
if (!nextLabel)
|
|
60
|
+
return;
|
|
61
|
+
setSaving(true);
|
|
62
|
+
setError(undefined);
|
|
63
|
+
void Promise.resolve(onAdd({ label: nextLabel, type })).then(() => {
|
|
64
|
+
setLabel('');
|
|
65
|
+
setType('text');
|
|
66
|
+
setOpen(false);
|
|
67
|
+
}).catch((cause) => setError(cause instanceof Error ? cause.message : 'Could not add this property')).finally(() => setSaving(false));
|
|
68
|
+
}, children: [_jsxs("label", { className: "grid gap-1 text-xs font-medium", children: ["Name", _jsx("input", { autoFocus: true, value: label, onChange: (event) => setLabel(event.target.value), className: "h-8 rounded-md border border-input bg-background px-2 text-sm font-normal outline-none focus-visible:ring-1 focus-visible:ring-ring" })] }), _jsxs("label", { className: "grid gap-1 text-xs font-medium", children: ["Type", _jsx(MenuSelect, { ariaLabel: "Property type", options: [
|
|
69
|
+
{ value: 'text', label: 'Text' },
|
|
70
|
+
{ value: 'markdown', label: 'Markdown' },
|
|
71
|
+
{ value: 'html', label: 'HTML' },
|
|
72
|
+
{ value: 'number', label: 'Number' },
|
|
73
|
+
{ value: 'checkbox', label: 'Checkbox' },
|
|
74
|
+
{ value: 'date', label: 'Date' },
|
|
75
|
+
{ value: 'select', label: 'Select' },
|
|
76
|
+
{ value: 'multi-select', label: 'Multi-select' },
|
|
77
|
+
], value: type, onChange: (next) => { if (next)
|
|
78
|
+
setType(next); } })] }), error && _jsx("p", { role: "alert", className: "m-0 text-xs text-destructive", children: error }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx("button", { type: "button", className: "h-8 rounded-md px-2 text-xs hover:bg-accent", onClick: close, children: "Cancel" }), _jsx("button", { type: "submit", disabled: saving || !label.trim(), className: "h-8 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground disabled:opacity-50", children: "Add property" })] })] }), portalContainer ?? document.body)] });
|
|
79
|
+
}
|
|
80
|
+
export function ColumnHeaderMenu({ activeFilter, activeSort, canHide, column, draggable = false, dragging = false, filterOperators = DEFAULT_FILTER_OPERATORS, filterValues = [], label, onFilter, onHide, onMoveFirst, onDelete, onRename, onSort, }) {
|
|
81
|
+
const portalContainer = useJsonViewsPortalContainer();
|
|
82
|
+
const [open, setOpen] = useState(false);
|
|
83
|
+
const [draftLabel, setDraftLabel] = useState(label);
|
|
84
|
+
const [saving, setSaving] = useState(false);
|
|
85
|
+
const [error, setError] = useState();
|
|
86
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
87
|
+
const [editingFilter, setEditingFilter] = useState(false);
|
|
88
|
+
const [editingSort, setEditingSort] = useState(false);
|
|
89
|
+
const [draftFilterOperator, setDraftFilterOperator] = useState(activeFilter?.operator ?? filterOperators[0]?.value ?? 'eq');
|
|
90
|
+
const [draftFilterValue, setDraftFilterValue] = useState(() => filterValueText(activeFilter?.value, activeFilter?.operator));
|
|
91
|
+
const filterValueListId = useId();
|
|
92
|
+
const triggerRef = useRef(null);
|
|
93
|
+
const menuRef = useRef(null);
|
|
94
|
+
const sortTriggerRef = useRef(null);
|
|
95
|
+
const sortMenuRef = useRef(null);
|
|
96
|
+
const menuPosition = useAnchoredPosition(open, triggerRef, menuRef);
|
|
97
|
+
const sortPosition = useAnchoredPosition(open && editingSort, sortTriggerRef, sortMenuRef, 'right');
|
|
98
|
+
const renameTimerRef = useRef(undefined);
|
|
99
|
+
const renameVersionRef = useRef(0);
|
|
100
|
+
const sort = activeSort?.column === column ? activeSort.direction : undefined;
|
|
101
|
+
useEffect(() => setDraftLabel(label), [label]);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (dragging)
|
|
104
|
+
setOpen(false);
|
|
105
|
+
}, [dragging]);
|
|
106
|
+
useEffect(() => () => {
|
|
107
|
+
if (renameTimerRef.current !== undefined)
|
|
108
|
+
clearTimeout(renameTimerRef.current);
|
|
109
|
+
}, []);
|
|
110
|
+
const run = async (action) => {
|
|
111
|
+
setSaving(true);
|
|
112
|
+
setError(undefined);
|
|
113
|
+
try {
|
|
114
|
+
await action();
|
|
115
|
+
setOpen(false);
|
|
116
|
+
}
|
|
117
|
+
catch (actionError) {
|
|
118
|
+
setError(actionError instanceof Error ? actionError.message : 'Could not update this column');
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
setSaving(false);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const saveRename = (value, delay = 0) => {
|
|
125
|
+
if (renameTimerRef.current !== undefined)
|
|
126
|
+
clearTimeout(renameTimerRef.current);
|
|
127
|
+
const next = value.trim();
|
|
128
|
+
if (!onRename || !next || next === label)
|
|
129
|
+
return;
|
|
130
|
+
const version = ++renameVersionRef.current;
|
|
131
|
+
const save = () => {
|
|
132
|
+
renameTimerRef.current = undefined;
|
|
133
|
+
setSaving(true);
|
|
134
|
+
void Promise.resolve(onRename(column, next)).then(() => {
|
|
135
|
+
if (version === renameVersionRef.current)
|
|
136
|
+
setError(undefined);
|
|
137
|
+
}).catch((renameError) => {
|
|
138
|
+
if (version === renameVersionRef.current)
|
|
139
|
+
setError(renameError instanceof Error ? renameError.message : 'Could not rename this column');
|
|
140
|
+
}).finally(() => {
|
|
141
|
+
if (version === renameVersionRef.current)
|
|
142
|
+
setSaving(false);
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
if (delay > 0)
|
|
146
|
+
renameTimerRef.current = setTimeout(save, delay);
|
|
147
|
+
else
|
|
148
|
+
save();
|
|
149
|
+
};
|
|
150
|
+
const openMenu = () => {
|
|
151
|
+
setDraftLabel(label);
|
|
152
|
+
setError(undefined);
|
|
153
|
+
setConfirmDelete(false);
|
|
154
|
+
setEditingFilter(false);
|
|
155
|
+
setEditingSort(false);
|
|
156
|
+
setDraftFilterOperator(activeFilter?.operator ?? filterOperators[0]?.value ?? 'eq');
|
|
157
|
+
setDraftFilterValue(filterValueText(activeFilter?.value, activeFilter?.operator));
|
|
158
|
+
setOpen(true);
|
|
159
|
+
};
|
|
160
|
+
const closeMenu = () => {
|
|
161
|
+
setOpen(false);
|
|
162
|
+
saveRename(draftLabel);
|
|
163
|
+
};
|
|
164
|
+
useDismiss({ enabled: open, onDismiss: closeMenu, insideRefs: [triggerRef, menuRef] });
|
|
165
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: triggerRef, type: "button", "data-id": "tabular-column-menu", "aria-label": `Column options for ${label}`, "aria-expanded": open, "aria-haspopup": "menu", draggable: draggable, className: "flex h-full min-h-9 w-full min-w-0 items-center px-3 py-2 text-left transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", title: label, onClick: openMenu, children: [_jsx("span", { className: "block min-w-0 flex-1 overflow-hidden whitespace-nowrap", children: label }), activeFilter && _jsx(Filter, { "data-id": "tabular-column-filter-active", "aria-hidden": true, className: "ml-1.5 h-3 w-3 shrink-0 text-primary" })] }), open && typeof document !== 'undefined' && createPortal(_jsxs("div", { ref: menuRef, role: "menu", className: "z-[100] w-60 rounded-md border border-border bg-card p-1.5 text-foreground shadow-lg", style: menuPosition, children: [confirmDelete ? _jsxs("div", { className: "grid gap-2 p-1", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Delete ", label, " from every row?"] }), _jsxs("div", { className: "flex justify-end gap-1.5", children: [_jsx("button", { type: "button", className: "rounded-md px-2 py-1.5 text-xs hover:bg-accent", onClick: () => setConfirmDelete(false), children: "Cancel" }), _jsx("button", { type: "button", "data-id": "tabular-confirm-delete-column", disabled: saving, className: "rounded-md border border-destructive-border bg-destructive-surface px-2 py-1.5 text-xs font-medium text-destructive-foreground hover:brightness-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:opacity-50", onClick: () => { if (onDelete)
|
|
166
|
+
void run(() => onDelete(column)); }, children: "Delete property" })] })] }) : editingFilter ? _jsxs("form", { className: "grid gap-2 p-1", "aria-label": `Filter ${label}`, onSubmit: (event) => {
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
if (!onFilter)
|
|
169
|
+
return;
|
|
170
|
+
const presenceOnly = draftFilterOperator === 'isEmpty' || draftFilterOperator === 'isNotEmpty';
|
|
171
|
+
void run(() => onFilter(column, {
|
|
172
|
+
operator: draftFilterOperator,
|
|
173
|
+
...(presenceOnly ? {} : { value: draftFilterValue }),
|
|
174
|
+
}));
|
|
175
|
+
}, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Filter, { className: "h-3.5 w-3.5 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-xs font-medium", children: ["Filter ", label] })] }), _jsxs("label", { className: "grid gap-1 text-[0.6875rem] text-muted-foreground", children: ["Condition", _jsx(MenuSelect, { ariaLabel: `Filter ${label} condition`, options: filterOperators, value: draftFilterOperator, onChange: setDraftFilterOperator })] }), draftFilterOperator !== 'isEmpty' && draftFilterOperator !== 'isNotEmpty' && _jsxs("label", { className: "grid gap-1 text-[0.6875rem] text-muted-foreground", children: ["Value", _jsx("input", { autoFocus: true, "aria-label": `Filter ${label} value`, list: filterValueListId, className: "h-8 w-full rounded-md border border-input bg-background px-2 text-xs text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring", placeholder: draftFilterOperator === 'in' || draftFilterOperator === 'notIn' ? 'Comma-separated values' : 'Value', value: draftFilterValue, onChange: (event) => setDraftFilterValue(event.target.value) }), filterValues.length > 0 && _jsx("datalist", { id: filterValueListId, children: filterValues.map((value, index) => _jsx("option", { value: filterValueText(value) }, `${filterValueText(value)}:${index}`)) })] }), _jsxs("div", { className: "flex justify-between gap-2 pt-1", children: [_jsx("button", { type: "button", "data-id": "tabular-clear-filter", disabled: saving || !activeFilter, className: "h-8 rounded-md px-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40", onClick: () => { if (onFilter)
|
|
176
|
+
void run(() => onFilter(column, null)); }, children: "Clear" }), _jsxs("div", { className: "flex gap-1", children: [_jsx("button", { type: "button", className: "h-8 rounded-md px-2 text-xs hover:bg-accent", onClick: () => setEditingFilter(false), children: "Back" }), _jsx("button", { type: "submit", "data-id": "tabular-apply-filter", disabled: saving || ((draftFilterOperator !== 'isEmpty' && draftFilterOperator !== 'isNotEmpty') && !draftFilterValue.trim()), className: "h-8 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground disabled:opacity-50", children: "Apply" })] })] })] }) : _jsxs(_Fragment, { children: [onRename && (_jsx("div", { className: "mb-1", children: _jsx("input", { "aria-label": `Rename ${label}`, className: "h-8 w-full rounded-md border border-input bg-background px-2 text-xs text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring", value: draftLabel, onChange: (event) => {
|
|
177
|
+
setDraftLabel(event.target.value);
|
|
178
|
+
saveRename(event.target.value, 300);
|
|
179
|
+
}, onBlur: (event) => saveRename(event.target.value) }) })), onRename && _jsx("div", { className: "-mx-1.5 my-1 h-px bg-border" }), onSort && (_jsxs("div", { role: "none", className: "relative", children: [_jsxs("button", { ref: sortTriggerRef, type: "button", role: "menuitem", "data-id": "tabular-sort-column", "aria-haspopup": "menu", "aria-expanded": editingSort, disabled: saving, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => setEditingSort((current) => !current), children: [_jsx(ArrowUpDown, { className: "mr-2 h-3.5 w-3.5" }), " Sort ", _jsx(ChevronRight, { className: "ml-auto h-3.5 w-3.5 text-muted-foreground" })] }), editingSort && (_jsxs("div", { ref: sortMenuRef, role: "menu", "aria-label": `Sort ${label}`, className: "z-[101] w-52 rounded-md border border-border bg-card p-1.5 text-foreground shadow-lg", style: sortPosition, children: [_jsxs("button", { type: "button", role: "menuitem", "data-id": "tabular-sort-ascending", disabled: saving, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => { void run(() => onSort(column, 'asc')); }, children: [_jsx(ChevronUp, { className: "mr-2 h-3.5 w-3.5" }), " Sort ascending"] }), _jsxs("button", { type: "button", role: "menuitem", "data-id": "tabular-sort-descending", disabled: saving, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => { void run(() => onSort(column, 'desc')); }, children: [_jsx(ChevronDown, { className: "mr-2 h-3.5 w-3.5" }), " Sort descending"] }), sort && _jsxs(_Fragment, { children: [_jsx("div", { className: "-mx-1.5 my-1 h-px bg-border" }), _jsxs("button", { type: "button", role: "menuitem", "data-id": "tabular-clear-sort", disabled: saving, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => { void run(() => onSort(column, null)); }, children: [_jsx(X, { className: "mr-2 h-3.5 w-3.5" }), " Clear sort"] })] })] }))] })), onFilter && (_jsxs(_Fragment, { children: [_jsx("div", { className: "-mx-1.5 my-1 h-px bg-border" }), _jsxs("button", { type: "button", role: "menuitem", "data-id": "tabular-filter-column", disabled: saving, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => { setEditingSort(false); setEditingFilter(true); }, children: [_jsx(Filter, { className: "mr-2 h-3.5 w-3.5" }), " ", activeFilter ? 'Edit filter' : 'Filter'] })] })), onMoveFirst && _jsxs("button", { type: "button", role: "menuitem", disabled: saving, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => { void run(onMoveFirst); }, children: [_jsx(ArrowLeftToLine, { className: "mr-2 h-3.5 w-3.5" }), " Move first"] }), onHide && (_jsxs(_Fragment, { children: [_jsx("div", { className: "-mx-1.5 my-1 h-px bg-border" }), _jsxs("button", { type: "button", role: "menuitem", "data-id": "tabular-hide-column", disabled: saving || !canHide, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => { void run(() => onHide(column)); }, children: [_jsx(EyeOff, { className: "mr-2 h-3.5 w-3.5" }), " Hide column"] })] })), onDelete && _jsxs(_Fragment, { children: [_jsx("div", { className: "-mx-1.5 my-1 h-px bg-border" }), _jsxs("button", { type: "button", role: "menuitem", "data-id": "tabular-delete-column", disabled: saving, className: "flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-left text-sm text-destructive outline-none transition-colors hover:bg-accent focus:bg-accent disabled:pointer-events-none disabled:opacity-50", onClick: () => setConfirmDelete(true), children: [_jsx(Trash2, { className: "mr-2 h-3.5 w-3.5" }), " Delete property"] })] })] }), error && _jsx("p", { role: "alert", className: "px-2 py-1 text-xs text-destructive", children: error })] }), portalContainer ?? document.body)] }));
|
|
180
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function CopyJsonAction({ className, dataId, getPathText, getRecordText, label, pathLabel, recordLabel, tone, }: {
|
|
2
|
+
className?: string;
|
|
3
|
+
dataId: string;
|
|
4
|
+
getPathText: () => string;
|
|
5
|
+
getRecordText: () => string;
|
|
6
|
+
label: string;
|
|
7
|
+
pathLabel: 'path' | 'paths';
|
|
8
|
+
recordLabel: 'record' | 'records';
|
|
9
|
+
tone?: 'default' | 'strong';
|
|
10
|
+
}): import("react").JSX.Element;
|