@calliopelabs/ui 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +94 -1
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +2 -0
  4. package/dist/primitives/Menubar.d.ts +21 -0
  5. package/dist/primitives/Menubar.js +43 -0
  6. package/dist/provider/messages.d.ts +148 -0
  7. package/dist/provider/messages.js +178 -0
  8. package/dist/shell/RailPanel.js +1 -1
  9. package/dist/workbench/ActivityBar.d.ts +37 -0
  10. package/dist/workbench/ActivityBar.js +57 -0
  11. package/dist/workbench/BottomPanel.d.ts +30 -0
  12. package/dist/workbench/BottomPanel.js +20 -0
  13. package/dist/workbench/CodeEditor.d.ts +48 -0
  14. package/dist/workbench/CodeEditor.js +153 -0
  15. package/dist/workbench/CommandPalette.d.ts +49 -0
  16. package/dist/workbench/CommandPalette.js +114 -0
  17. package/dist/workbench/DataChart.d.ts +45 -0
  18. package/dist/workbench/DataChart.js +146 -0
  19. package/dist/workbench/DataGrid.d.ts +89 -0
  20. package/dist/workbench/DataGrid.js +590 -0
  21. package/dist/workbench/DataProfile.d.ts +17 -0
  22. package/dist/workbench/DataProfile.js +51 -0
  23. package/dist/workbench/EditorTabs.d.ts +62 -0
  24. package/dist/workbench/EditorTabs.js +115 -0
  25. package/dist/workbench/GridCell.d.ts +19 -0
  26. package/dist/workbench/GridCell.js +50 -0
  27. package/dist/workbench/GridDetails.d.ts +31 -0
  28. package/dist/workbench/GridDetails.js +78 -0
  29. package/dist/workbench/MenuBar.d.ts +49 -0
  30. package/dist/workbench/MenuBar.js +25 -0
  31. package/dist/workbench/PaneGroup.d.ts +50 -0
  32. package/dist/workbench/PaneGroup.js +146 -0
  33. package/dist/workbench/PendingChangesBar.d.ts +20 -0
  34. package/dist/workbench/PendingChangesBar.js +19 -0
  35. package/dist/workbench/Shortcuts.d.ts +50 -0
  36. package/dist/workbench/Shortcuts.js +109 -0
  37. package/dist/workbench/SqlEditor.d.ts +21 -0
  38. package/dist/workbench/SqlEditor.js +79 -0
  39. package/dist/workbench/StatusBar.d.ts +28 -0
  40. package/dist/workbench/StatusBar.js +26 -0
  41. package/dist/workbench/TruncationBanner.d.ts +11 -0
  42. package/dist/workbench/TruncationBanner.js +12 -0
  43. package/dist/workbench/WorkbenchShell.d.ts +36 -0
  44. package/dist/workbench/WorkbenchShell.js +20 -0
  45. package/dist/workbench/download.d.ts +11 -0
  46. package/dist/workbench/download.js +28 -0
  47. package/dist/workbench/editor-theme.d.ts +42 -0
  48. package/dist/workbench/editor-theme.js +113 -0
  49. package/dist/workbench/grid-format.d.ts +59 -0
  50. package/dist/workbench/grid-format.js +169 -0
  51. package/dist/workbench/grid-model.d.ts +75 -0
  52. package/dist/workbench/grid-model.js +259 -0
  53. package/dist/workbench/grid-selection.d.ts +69 -0
  54. package/dist/workbench/grid-selection.js +183 -0
  55. package/dist/workbench/grid-stats.d.ts +58 -0
  56. package/dist/workbench/grid-stats.js +185 -0
  57. package/dist/workbench/index.d.ts +28 -0
  58. package/dist/workbench/index.js +31 -0
  59. package/dist/workbench/keys.d.ts +23 -0
  60. package/dist/workbench/keys.js +105 -0
  61. package/dist/workbench/palette.d.ts +17 -0
  62. package/dist/workbench/palette.js +45 -0
  63. package/dist/workbench/pending.d.ts +92 -0
  64. package/dist/workbench/pending.js +207 -0
  65. package/dist/workbench/sql-completion.d.ts +107 -0
  66. package/dist/workbench/sql-completion.js +475 -0
  67. package/dist/workflow/WorkflowRuntimeControls.d.ts +21 -0
  68. package/dist/workflow/WorkflowRuntimeControls.js +54 -0
  69. package/dist/workflow/runtime-controls.d.ts +29 -0
  70. package/dist/workflow/runtime-controls.js +10 -0
  71. package/package.json +13 -2
@@ -0,0 +1,146 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ActivityIcon, ChartColumnIcon, ChartPieIcon, CircleIcon, TrendingUpIcon, } from "lucide-react";
4
+ import * as React from "react";
5
+ import { cn } from "../lib/cn.js";
6
+ import { Segmented } from "../primitives/Segmented.js";
7
+ import { Select } from "../primitives/Select.js";
8
+ import { useUI } from "../provider/UIProvider.js";
9
+ import { EmptyState } from "../states/EmptyState.js";
10
+ import { ErrorState } from "../states/ErrorState.js";
11
+ import { LoadingState } from "../states/LoadingState.js";
12
+ import { columnSignature } from "./grid-model.js";
13
+ import { TruncationBanner } from "./TruncationBanner.js";
14
+ /** Recharts puts every point in the DOM, so the chart plots a prefix. */
15
+ const CHART_ROW_CAP = 500;
16
+ /** The five chart series, from the theme: they follow the ground and the accent. */
17
+ const SERIES = [
18
+ "var(--chart-1)",
19
+ "var(--chart-2)",
20
+ "var(--chart-3)",
21
+ "var(--chart-4)",
22
+ "var(--chart-5)",
23
+ ];
24
+ const KIND_ICON = {
25
+ bar: ChartColumnIcon,
26
+ line: TrendingUpIcon,
27
+ area: ActivityIcon,
28
+ scatter: CircleIcon,
29
+ pie: ChartPieIcon,
30
+ };
31
+ function isNumeric(value) {
32
+ if (typeof value === "number")
33
+ return Number.isFinite(value);
34
+ return typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value));
35
+ }
36
+ /** Columns mostly numeric in a sample of their first values, and the rest. */
37
+ function classify(names, rows) {
38
+ const numeric = [];
39
+ const categorical = [];
40
+ for (const name of names) {
41
+ const sample = [];
42
+ for (const row of rows) {
43
+ const v = row[name];
44
+ if (v !== null && v !== undefined)
45
+ sample.push(v);
46
+ if (sample.length >= 20)
47
+ break;
48
+ }
49
+ const share = sample.length > 0 ? sample.filter(isNumeric).length / sample.length : 0;
50
+ (share > 0.7 ? numeric : categorical).push(name);
51
+ }
52
+ return { numeric, categorical };
53
+ }
54
+ /**
55
+ * A quick chart of a result: bar, line, area, scatter or pie, an X column
56
+ * and one or more Y columns, coloured by the theme's chart series. Recharts
57
+ * is the host's to supply (`loadCharts`); without it nothing is loaded.
58
+ */
59
+ export function DataChart(props) {
60
+ return _jsx(ChartSurface, { ...props }, columnSignature(props.columns));
61
+ }
62
+ function ChartSurface({ columns, rows, loadCharts, truncated, rowCap = CHART_ROW_CAP, className, }) {
63
+ const { messages } = useUI();
64
+ const t = messages.workbench;
65
+ const names = React.useMemo(() => columns.map((c) => (typeof c === "string" ? c : c.name)), [columns]);
66
+ const { numeric, categorical } = React.useMemo(() => classify(names, rows), [names, rows]);
67
+ const [kind, setKind] = React.useState("bar");
68
+ const [x, setX] = React.useState(() => categorical[0] ?? names[0] ?? "");
69
+ const [ys, setYs] = React.useState(() => {
70
+ const first = numeric.find((n) => n !== (categorical[0] ?? names[0])) ?? names[1];
71
+ return first ? [first] : [];
72
+ });
73
+ const [lib, setLib] = React.useState(null);
74
+ const [failed, setFailed] = React.useState(null);
75
+ const [attempt, setAttempt] = React.useState(0);
76
+ const loader = React.useRef(loadCharts);
77
+ React.useEffect(() => {
78
+ let live = true;
79
+ loader.current().then((module) => live && setLib(module), (reason) => live && setFailed(reason instanceof Error ? reason.message : String(reason)));
80
+ return () => {
81
+ live = false;
82
+ };
83
+ }, [attempt]);
84
+ const data = React.useMemo(() => rows.slice(0, rowCap).map((row) => {
85
+ const entry = { [x]: row[x] };
86
+ for (const y of ys) {
87
+ const v = row[y];
88
+ entry[y] = typeof v === "number" ? v : Number(v) || 0;
89
+ }
90
+ return entry;
91
+ }), [rows, rowCap, x, ys]);
92
+ if (rows.length === 0)
93
+ return _jsx(EmptyState, { title: t.chartNoData, className: className });
94
+ const notes = [truncated, rows.length > rowCap ? t.chartCap(rowCap, rows.length) : null].filter(Boolean);
95
+ const tick = { fontSize: 11, fill: "var(--muted-foreground)" };
96
+ const tooltip = {
97
+ contentStyle: {
98
+ backgroundColor: "var(--popover)",
99
+ border: "1px solid var(--border)",
100
+ borderRadius: "var(--radius)",
101
+ color: "var(--popover-foreground)",
102
+ fontSize: 12,
103
+ },
104
+ itemStyle: { color: "var(--popover-foreground)" },
105
+ labelStyle: { color: "var(--muted-foreground)" },
106
+ cursor: { fill: "var(--muted)", stroke: "var(--border)" },
107
+ };
108
+ const legend = { wrapperStyle: { fontSize: 12, color: "var(--muted-foreground)" } };
109
+ let chart;
110
+ if (failed) {
111
+ chart = (_jsx(ErrorState, { title: t.chartLoadFailed, error: failed, onRetry: () => {
112
+ setFailed(null);
113
+ setAttempt((n) => n + 1);
114
+ }, retryLabel: t.retry }));
115
+ }
116
+ else if (!lib) {
117
+ chart = _jsx(LoadingState, { label: t.chartLoading, variant: "rows", rows: 4 });
118
+ }
119
+ else if (ys.length === 0) {
120
+ chart = _jsx(EmptyState, { title: t.chartPickY });
121
+ }
122
+ else {
123
+ const L = lib;
124
+ const axes = (_jsxs(_Fragment, { children: [_jsx(L.CartesianGrid, { strokeDasharray: "3 3", stroke: "var(--border)" }), _jsx(L.XAxis, { dataKey: x, tick: tick, tickLine: false, stroke: "var(--border)" }), _jsx(L.YAxis, { tick: tick, tickLine: false, stroke: "var(--border)" }), _jsx(L.Tooltip, { ...tooltip }), ys.length > 1 && _jsx(L.Legend, { ...legend })] }));
125
+ const series = (i) => SERIES[i % SERIES.length];
126
+ chart = (_jsx(L.ResponsiveContainer, { width: "100%", height: "100%", children: kind === "bar" ? (_jsxs(L.BarChart, { data: data, children: [axes, ys.map((y, i) => (_jsx(L.Bar, { dataKey: y, fill: series(i), radius: [2, 2, 0, 0], isAnimationActive: false }, y)))] })) : kind === "line" ? (_jsxs(L.LineChart, { data: data, children: [axes, ys.map((y, i) => (_jsx(L.Line, { dataKey: y, stroke: series(i), strokeWidth: 2, dot: { r: 2, fill: series(i) }, isAnimationActive: false }, y)))] })) : kind === "area" ? (_jsxs(L.AreaChart, { data: data, children: [axes, ys.map((y, i) => (_jsx(L.Area, { dataKey: y, stroke: series(i), fill: series(i), fillOpacity: 0.2, strokeWidth: 2, isAnimationActive: false }, y)))] })) : kind === "scatter" ? (_jsxs(L.ScatterChart, { children: [_jsx(L.CartesianGrid, { strokeDasharray: "3 3", stroke: "var(--border)" }), _jsx(L.XAxis, { dataKey: x, name: x, type: "number", tick: tick, tickLine: false, stroke: "var(--border)" }), _jsx(L.YAxis, { dataKey: ys[0], name: ys[0], tick: tick, tickLine: false, stroke: "var(--border)" }), _jsx(L.Tooltip, { ...tooltip }), _jsx(L.Scatter, { data: data, fill: series(0), isAnimationActive: false })] })) : (_jsxs(L.PieChart, { children: [_jsx(L.Tooltip, { ...tooltip }), _jsx(L.Legend, { ...legend }), _jsx(L.Pie, { data: data.map((d) => ({
127
+ name: String(d[x] ?? ""),
128
+ value: Number(d[ys[0] ?? ""]) || 0,
129
+ })), dataKey: "value", nameKey: "name", outerRadius: "80%", stroke: "var(--background)", isAnimationActive: false, children: data.map((_, i) => (_jsx(L.Cell, { fill: series(i) }, i))) })] })) }));
130
+ }
131
+ return (_jsxs("section", { "aria-label": t.chartLabel, "data-slot": "data-chart", className: cn("flex min-h-0 flex-col", className), children: [notes.length > 0 && _jsx(TruncationBanner, { children: notes.join(" ") }), _jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-3 border-b bg-muted/40 px-3 py-1.5 text-xs", children: [_jsx(Segmented, { label: t.chartType, size: "sm", value: kind, onValueChange: setKind, options: ["bar", "line", "area", "scatter", "pie"].map((k) => ({
132
+ value: k,
133
+ label: t.chartKind(k),
134
+ icon: KIND_ICON[k],
135
+ })) }), _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: t.chartX }), _jsx(Select, { size: "sm", "aria-label": t.chartX, value: x, onValueChange: (next) => {
136
+ setX(next);
137
+ setYs((prev) => prev.filter((y) => y !== next));
138
+ }, options: names.map((n) => ({ value: n, label: n })) })] }), _jsxs("fieldset", { className: "flex min-w-0 flex-wrap items-center gap-1", children: [_jsx("legend", { className: "sr-only", children: t.chartY }), _jsx("span", { className: "mr-0.5 text-muted-foreground", "aria-hidden": true, children: t.chartY }), names
139
+ .filter((n) => n !== x)
140
+ .map((name) => {
141
+ const on = ys.includes(name);
142
+ return (_jsxs("button", { type: "button", "aria-pressed": on, className: cn("flex items-center gap-1 rounded-full border px-2 py-0.5 outline-none focus-visible:ring-2 focus-visible:ring-ring/50", on
143
+ ? "border-foreground/30 text-foreground"
144
+ : "text-muted-foreground hover:text-foreground"), onClick: () => setYs((prev) => (on ? prev.filter((y) => y !== name) : [...prev, name])), children: [on && (_jsx("span", { "aria-hidden": true, className: "size-2 rounded-full", style: { backgroundColor: SERIES[ys.indexOf(name) % SERIES.length] } })), name] }, name));
145
+ })] })] }), _jsx("div", { className: "min-h-64 flex-1 p-4", children: chart })] }));
146
+ }
@@ -0,0 +1,89 @@
1
+ import * as React from "react";
2
+ import { type CellMatrix, type CopyFormat, type ExportFile, type ExportFormat } from "./grid-format.js";
3
+ import { type GridColumn, type GridRow } from "./grid-model.js";
4
+ import { type ChangeSet, type PendingChanges } from "./pending.js";
5
+ /** A format the host adds to the export menu (a workbook); the host writes the file. */
6
+ export interface GridExportOption {
7
+ id: string;
8
+ label: string;
9
+ }
10
+ /** An export the person asked for. `file` is the library's text for its own formats, null for a host's. */
11
+ export interface GridExportRequest {
12
+ format: ExportFormat | (string & {});
13
+ matrix: CellMatrix;
14
+ file: ExportFile | null;
15
+ }
16
+ /** A copy the grid put on the clipboard. */
17
+ export interface GridCopyEvent {
18
+ format: CopyFormat;
19
+ text: string;
20
+ /** Cells copied: rows times columns. */
21
+ cells: number;
22
+ }
23
+ /** A commit's failure, as the host rejects `onCommit` with it. `key` is the row key in the change set. */
24
+ export interface GridCommitError {
25
+ message: string;
26
+ key?: number;
27
+ }
28
+ export interface DataGridProps {
29
+ /** Names, or descriptions with a type, keys and nullability. Undeclared types are sniffed from the rows. */
30
+ columns: readonly (string | GridColumn)[];
31
+ rows: readonly GridRow[];
32
+ /** The grid's accessible name. */
33
+ label?: string;
34
+ loading?: boolean;
35
+ error?: string | {
36
+ message: string;
37
+ } | null;
38
+ onRetry?: () => void;
39
+ /** The server's total when it capped the rows; with `hasMore`, the banner says more exist. */
40
+ totalRows?: number | null;
41
+ hasMore?: boolean;
42
+ /**
43
+ * Remembers column widths per person (the library's local store). The
44
+ * grid also starts clean (selection, sort, filter, hidden columns) when it
45
+ * changes, or when the columns do.
46
+ */
47
+ storageKey?: string;
48
+ /** The table for "Copy as INSERT" and the SQL export. */
49
+ tableName?: string;
50
+ /** The exported file's name without extension. */
51
+ exportName?: string;
52
+ /** After a copy reached the clipboard: the host may say so. */
53
+ onCopy?: (event: GridCopyEvent) => void;
54
+ /**
55
+ * Takes over export: the grid hands every request here instead of
56
+ * downloading (`downloadExport(request.file)` downloads one). Required for
57
+ * `exportFormats`.
58
+ */
59
+ onExport?: (request: GridExportRequest) => void;
60
+ /** Formats the host writes itself (xlsx), after the library's own. */
61
+ exportFormats?: readonly GridExportOption[];
62
+ /**
63
+ * Cells edit in place (Enter, F2, double click or typing), rows add,
64
+ * duplicate and delete, and the changes wait in a bar to be committed.
65
+ * Existing rows change only when a column is a `primaryKey`.
66
+ */
67
+ editable?: boolean;
68
+ /**
69
+ * Writes the changes. Resolve and the grid clears them; reject (with a
70
+ * `GridCommitError` or an Error) and they stay, with the message shown.
71
+ */
72
+ onCommit?: (changes: ChangeSet) => void | Promise<void>;
73
+ /** Controlled pending changes; leave out to let the grid keep them. */
74
+ pending?: PendingChanges;
75
+ onPendingChange?: (pending: PendingChanges) => void;
76
+ /** Host controls at the end of the toolbar (an edit-mode switch, a refresh). */
77
+ toolbar?: React.ReactNode;
78
+ className?: string;
79
+ }
80
+ /**
81
+ * A data grid for query results and table data: rows and columns both
82
+ * virtualized (50,000 rows by 30 columns is a few hundred nodes), the
83
+ * WAI-ARIA grid pattern for the keyboard, cell, row and column selection,
84
+ * sort (NULLs last), filter, hidden and resizable columns, copy and export
85
+ * in six text formats, selection statistics, cell and row detail, and
86
+ * optional editing with pending changes the host commits. Host-driven: the
87
+ * rows come in, changes go out through callbacks, nothing fetches.
88
+ */
89
+ export declare function DataGrid(props: DataGridProps): React.JSX.Element;