@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,57 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../primitives/Tooltip.js";
6
+ import { useUI } from "../provider/UIProvider.js";
7
+ /**
8
+ * The rail of views down the left of an IDE layout: a vertical toolbar with
9
+ * one tab stop (Up and Down move, Home and End jump), each item a toggle
10
+ * button that says whether its view is open.
11
+ */
12
+ export function ActivityBar({ items, footer = [], active, onActiveChange, showLabels = true, className, }) {
13
+ const { messages } = useUI();
14
+ const all = [...items, ...footer];
15
+ const [focusId, setFocusId] = React.useState(undefined);
16
+ const tabStop = all.some((i) => i.id === focusId)
17
+ ? focusId
18
+ : (all.find((i) => i.id === active)?.id ?? all[0]?.id);
19
+ const refs = React.useRef(new Map());
20
+ const onKeyDown = (event) => {
21
+ const index = all.findIndex((i) => i.id === tabStop);
22
+ let next = null;
23
+ if (event.key === "ArrowDown")
24
+ next = (index + 1) % all.length;
25
+ else if (event.key === "ArrowUp")
26
+ next = (index - 1 + all.length) % all.length;
27
+ else if (event.key === "Home")
28
+ next = 0;
29
+ else if (event.key === "End")
30
+ next = all.length - 1;
31
+ if (next === null)
32
+ return;
33
+ event.preventDefault();
34
+ const id = all[next]?.id;
35
+ if (id === undefined)
36
+ return;
37
+ setFocusId(id);
38
+ refs.current.get(id)?.focus();
39
+ };
40
+ const button = (item) => {
41
+ const pressed = item.pressed ?? item.id === active;
42
+ const title = item.title ?? item.label;
43
+ const count = typeof item.badge === "string" || typeof item.badge === "number" ? ` (${item.badge})` : "";
44
+ return (_jsxs(Tooltip, { delayDuration: 400, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { ref: (el) => {
45
+ if (el)
46
+ refs.current.set(item.id, el);
47
+ else
48
+ refs.current.delete(item.id);
49
+ }, type: "button", tabIndex: item.id === tabStop ? 0 : -1, "aria-pressed": pressed, "aria-label": `${title}${count}`, onFocus: () => setFocusId(item.id), onClick: () => {
50
+ if (item.onSelect)
51
+ item.onSelect();
52
+ else
53
+ onActiveChange(item.id === active ? null : item.id);
54
+ }, className: cn("relative flex w-full flex-col items-center gap-0.5 py-2 text-sidebar-muted-foreground outline-hidden transition-colors hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring focus-visible:ring-inset", pressed && "text-sidebar-primary hover:text-sidebar-primary"), children: [pressed && (_jsx("span", { "aria-hidden": true, className: "absolute top-1/2 left-0 h-8 w-0.5 -translate-y-1/2 rounded-full bg-sidebar-primary" })), _jsxs("span", { "aria-hidden": true, className: "relative flex [&_svg]:size-4.5", children: [item.icon, item.badge !== undefined && item.badge !== null && item.badge !== false && (_jsx("span", { className: "absolute -top-1.5 -right-2 min-w-4 rounded-full bg-sidebar-primary px-1 text-center text-2xs leading-4 text-sidebar-primary-foreground", children: item.badge }))] }), showLabels && (_jsx("span", { "aria-hidden": true, className: "text-2xs leading-none tracking-tight", children: item.label }))] }) }), _jsx(TooltipContent, { side: "right", children: title })] }, item.id));
55
+ };
56
+ return (_jsxs("div", { role: "toolbar", "aria-orientation": "vertical", "aria-label": messages.workbench.activityBar, onKeyDown: onKeyDown, className: cn("flex h-full w-14 shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground", className), children: [_jsx("div", { className: "flex w-full flex-col", children: items.map(button) }), footer.length > 0 && (_jsx("div", { className: "mt-auto flex w-full flex-col pb-2", children: footer.map(button) }))] }));
57
+ }
@@ -0,0 +1,30 @@
1
+ import type * as React from "react";
2
+ export interface BottomPanelView {
3
+ id: string;
4
+ label: string;
5
+ icon?: React.ReactNode;
6
+ /** A count beside the label (history entries, running tasks). */
7
+ count?: number;
8
+ /** Something is running in it: the count takes the info tone. */
9
+ busy?: boolean;
10
+ content: React.ReactNode;
11
+ }
12
+ export interface BottomPanelProps {
13
+ views: readonly BottomPanelView[];
14
+ /** The view on screen. Controlled when given. */
15
+ value?: string;
16
+ defaultValue?: string;
17
+ onValueChange?: (id: string) => void;
18
+ /** The panel fills the editor area. Shows the maximize or restore button when `onMaximizedChange` is set. */
19
+ maximized?: boolean;
20
+ onMaximizedChange?: (maximized: boolean) => void;
21
+ /** Shows a close button. */
22
+ onClose?: () => void;
23
+ className?: string;
24
+ }
25
+ /**
26
+ * The panel under the editors (history, tasks, output, problems): views as
27
+ * ARIA tabs, with maximize, restore and close. The host decides whether it
28
+ * is open and how tall it is (in a `PaneGroup`); views stay host content.
29
+ */
30
+ export declare function BottomPanel({ views, value, defaultValue, onValueChange, maximized, onMaximizedChange, onClose, className, }: BottomPanelProps): React.JSX.Element;
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Maximize2Icon, Minimize2Icon, XIcon } from "lucide-react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/Tabs.js";
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../primitives/Tooltip.js";
7
+ import { useUI } from "../provider/UIProvider.js";
8
+ function IconButton({ label, onClick, children, }) {
9
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label, onClick: onClick, className: "rounded-lg p-1 text-muted-foreground outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-3.5", children: children }) }), _jsx(TooltipContent, { children: label })] }));
10
+ }
11
+ /**
12
+ * The panel under the editors (history, tasks, output, problems): views as
13
+ * ARIA tabs, with maximize, restore and close. The host decides whether it
14
+ * is open and how tall it is (in a `PaneGroup`); views stay host content.
15
+ */
16
+ export function BottomPanel({ views, value, defaultValue, onValueChange, maximized = false, onMaximizedChange, onClose, className, }) {
17
+ const { messages } = useUI();
18
+ const m = messages.workbench;
19
+ return (_jsxs(Tabs, { value: value, defaultValue: defaultValue ?? views[0]?.id, onValueChange: onValueChange, className: cn("flex h-full min-h-0 flex-col gap-0 bg-background", className), children: [_jsxs("div", { className: "flex h-8 shrink-0 items-center border-b bg-surface-1 pr-1", children: [_jsx(TabsList, { className: "h-full flex-1 border-b-0", children: views.map((view) => (_jsxs(TabsTrigger, { value: view.id, className: "flex items-center gap-1.5 px-3 py-1 text-xs [&_svg]:size-3", children: [view.icon && (_jsx("span", { "aria-hidden": true, className: "flex", children: view.icon })), view.label, view.count !== undefined && view.count > 0 && (_jsxs(_Fragment, { children: [" ", _jsx("span", { className: cn("rounded-full px-1.5 text-2xs tabular-nums", view.busy ? "bg-info/15 text-info-fg" : "bg-muted text-muted-foreground"), children: view.count.toLocaleString() })] }))] }, view.id))) }), onMaximizedChange && (_jsx(IconButton, { label: maximized ? m.restorePanel : m.maximizePanel, onClick: () => onMaximizedChange(!maximized), children: maximized ? _jsx(Minimize2Icon, { "aria-hidden": true }) : _jsx(Maximize2Icon, { "aria-hidden": true }) })), onClose && (_jsx(IconButton, { label: m.closePanel, onClick: onClose, children: _jsx(XIcon, { "aria-hidden": true }) }))] }), views.map((view) => (_jsx(TabsContent, { value: view.id, className: "min-h-0 flex-1 overflow-hidden", children: view.content }, view.id)))] }));
20
+ }
@@ -0,0 +1,48 @@
1
+ import type * as Monaco from "monaco-editor";
2
+ import * as React from "react";
3
+ export type MonacoApi = typeof Monaco;
4
+ export type MonacoEditor = Monaco.editor.IStandaloneCodeEditor;
5
+ type Loader = () => Promise<MonacoApi | {
6
+ default: MonacoApi;
7
+ }>;
8
+ /**
9
+ * Tell the editors how to load Monaco, once, before any mounts:
10
+ * `configureMonaco({ load })`, where `load` imports `monaco-editor`. The import is
11
+ * the host's, so its bundler resolves Monaco, splits it into its own chunk
12
+ * and fetches it the first time an editor mounts; an app that never shows
13
+ * an editor never loads it, and one without `monaco-editor` installed still
14
+ * builds. The host also sets `self.MonacoEnvironment` for Monaco's workers
15
+ * (see the README). Without a loader, editors fall back to a plain text area.
16
+ */
17
+ export declare function configureMonaco(options: {
18
+ load: Loader;
19
+ }): void;
20
+ export interface CodeEditorProps {
21
+ value: string;
22
+ onChange?: (value: string) => void;
23
+ /** A Monaco language id: "sql", "json", "python", "typescript". Defaults to "plaintext". */
24
+ language?: string;
25
+ /** The editor's accessible name ("Query", "Pipeline spec"). */
26
+ label: string;
27
+ readOnly?: boolean;
28
+ /**
29
+ * Ctrl or Command with Enter: gets the selected text, or the whole buffer
30
+ * when nothing is selected.
31
+ */
32
+ onRun?: (text: string) => void;
33
+ /** Passed to Monaco as they are; the editor sets sensible defaults first. */
34
+ options?: Monaco.editor.IStandaloneEditorConstructionOptions;
35
+ /** The editor is ready. Return a cleanup to run when it unmounts. */
36
+ onMount?: (editor: MonacoEditor, monaco: MonacoApi) => void | (() => void);
37
+ /** Load Monaco for this editor only, instead of `configureMonaco`'s loader. Read on mount. */
38
+ loader?: Loader;
39
+ className?: string;
40
+ }
41
+ /**
42
+ * A code editor on Monaco, loaded the first time one mounts and themed from
43
+ * the library's tokens for the current ground (it follows a change of
44
+ * ground or mode). Monaco is an optional peer dependency: without it, or
45
+ * while it loads, a plain text area or a loading state stands in.
46
+ */
47
+ export declare function CodeEditor({ value, onChange, language, label, readOnly, onRun, options, onMount, loader, className, }: CodeEditorProps): React.JSX.Element;
48
+ export {};
@@ -0,0 +1,153 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { useUI } from "../provider/UIProvider.js";
6
+ import { LoadingState } from "../states/LoadingState.js";
7
+ import { editorTheme, readEditorPalette } from "./editor-theme.js";
8
+ let configured = null;
9
+ const loads = new WeakMap();
10
+ /**
11
+ * Tell the editors how to load Monaco, once, before any mounts:
12
+ * `configureMonaco({ load })`, where `load` imports `monaco-editor`. The import is
13
+ * the host's, so its bundler resolves Monaco, splits it into its own chunk
14
+ * and fetches it the first time an editor mounts; an app that never shows
15
+ * an editor never loads it, and one without `monaco-editor` installed still
16
+ * builds. The host also sets `self.MonacoEnvironment` for Monaco's workers
17
+ * (see the README). Without a loader, editors fall back to a plain text area.
18
+ */
19
+ export function configureMonaco(options) {
20
+ configured = options.load;
21
+ }
22
+ function loadMonaco(loader) {
23
+ let promise = loads.get(loader);
24
+ if (!promise) {
25
+ promise = loader().then((m) => ("default" in m && m.default ? m.default : m));
26
+ // A failed load may succeed later (a flaky network): do not cache it.
27
+ promise.catch(() => loads.delete(loader));
28
+ loads.set(loader, promise);
29
+ }
30
+ return promise;
31
+ }
32
+ const THEME = "calliope";
33
+ /** Define and apply the theme from the tokens under `element`. Monaco themes are page-wide. */
34
+ function applyTheme(monaco, element) {
35
+ const palette = readEditorPalette(element);
36
+ if (palette) {
37
+ monaco.editor.defineTheme(THEME, editorTheme(palette));
38
+ monaco.editor.setTheme(THEME);
39
+ }
40
+ else {
41
+ const dark = document.documentElement.getAttribute("data-mode") === "dark";
42
+ monaco.editor.setTheme(dark ? "vs-dark" : "vs");
43
+ }
44
+ }
45
+ /**
46
+ * A code editor on Monaco, loaded the first time one mounts and themed from
47
+ * the library's tokens for the current ground (it follows a change of
48
+ * ground or mode). Monaco is an optional peer dependency: without it, or
49
+ * while it loads, a plain text area or a loading state stands in.
50
+ */
51
+ export function CodeEditor({ value, onChange, language = "plaintext", label, readOnly = false, onRun, options, onMount, loader, className, }) {
52
+ const { messages } = useUI();
53
+ const hostRef = React.useRef(null);
54
+ const editorRef = React.useRef(null);
55
+ const monacoRef = React.useRef(null);
56
+ const latest = React.useRef({ value, onChange, onRun, onMount });
57
+ const [status, setStatus] = React.useState(() => (loader ?? configured) ? "loading" : "fallback");
58
+ React.useLayoutEffect(() => {
59
+ latest.current = { value, onChange, onRun, onMount };
60
+ });
61
+ // The loader of the first render: an inline arrow must not recreate the editor.
62
+ const loaderRef = React.useRef(loader ?? configured);
63
+ // Mount once. Value, language and read-only follow below.
64
+ React.useEffect(() => {
65
+ const load = loaderRef.current;
66
+ const host = hostRef.current;
67
+ if (!load || !host)
68
+ return;
69
+ let cancelled = false;
70
+ let cleanup;
71
+ let observer;
72
+ loadMonaco(load)
73
+ .then((monaco) => {
74
+ if (cancelled)
75
+ return;
76
+ monacoRef.current = monaco;
77
+ applyTheme(monaco, host);
78
+ const mono = getComputedStyle(host).getPropertyValue("--font-mono").trim();
79
+ const editor = monaco.editor.create(host, {
80
+ value: latest.current.value,
81
+ language,
82
+ readOnly,
83
+ ariaLabel: label,
84
+ automaticLayout: true,
85
+ minimap: { enabled: false },
86
+ scrollBeyondLastLine: false,
87
+ fontSize: 13,
88
+ fontFamily: mono || undefined,
89
+ tabSize: 2,
90
+ renderLineHighlight: "line",
91
+ fixedOverflowWidgets: true,
92
+ ...options,
93
+ });
94
+ editorRef.current = editor;
95
+ editor.onDidChangeModelContent(() => {
96
+ const next = editor.getValue();
97
+ if (next !== latest.current.value)
98
+ latest.current.onChange?.(next);
99
+ });
100
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
101
+ const selection = editor.getSelection();
102
+ const model = editor.getModel();
103
+ const picked = selection && model && !selection.isEmpty() ? model.getValueInRange(selection) : "";
104
+ latest.current.onRun?.(picked || editor.getValue());
105
+ });
106
+ // The theme follows the ground: re-read the tokens when <html> changes.
107
+ observer = new MutationObserver(() => applyTheme(monaco, host));
108
+ observer.observe(document.documentElement, { attributes: true });
109
+ const unmount = latest.current.onMount?.(editor, monaco);
110
+ cleanup = typeof unmount === "function" ? unmount : undefined;
111
+ setStatus("ready");
112
+ })
113
+ .catch(() => {
114
+ if (!cancelled)
115
+ setStatus("fallback");
116
+ });
117
+ return () => {
118
+ cancelled = true;
119
+ observer?.disconnect();
120
+ cleanup?.();
121
+ editorRef.current?.dispose();
122
+ editorRef.current = null;
123
+ };
124
+ // Recreating the editor on every option change would lose the undo stack
125
+ // and the cursor; those that can change are applied in place below.
126
+ // eslint-disable-next-line react-hooks/exhaustive-deps
127
+ }, []);
128
+ React.useEffect(() => {
129
+ const editor = editorRef.current;
130
+ const model = editor?.getModel();
131
+ if (!editor || !model || model.getValue() === value)
132
+ return;
133
+ // An edit, not setValue, so the person can undo past a host change.
134
+ editor.pushUndoStop();
135
+ model.pushEditOperations([], [{ range: model.getFullModelRange(), text: value }], () => null);
136
+ editor.pushUndoStop();
137
+ }, [value, status]);
138
+ React.useEffect(() => {
139
+ const model = editorRef.current?.getModel();
140
+ if (model && monacoRef.current)
141
+ monacoRef.current.editor.setModelLanguage(model, language);
142
+ }, [language, status]);
143
+ React.useEffect(() => {
144
+ editorRef.current?.updateOptions({ readOnly, ariaLabel: label });
145
+ }, [readOnly, label, status]);
146
+ return (_jsxs("div", { "data-slot": "code-editor", "data-status": status, className: cn("relative h-full min-h-24 w-full overflow-hidden bg-background", className), children: [_jsx("div", { ref: hostRef, className: cn("absolute inset-0", status !== "ready" && "invisible") }), status === "loading" && (_jsx(LoadingState, { className: "absolute inset-0", label: messages.workbench.editorLoading })), status === "fallback" && (_jsx("textarea", { "aria-label": label, value: value, readOnly: readOnly, spellCheck: false, onChange: (event) => onChange?.(event.target.value), onKeyDown: (event) => {
147
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey) && onRun) {
148
+ event.preventDefault();
149
+ const t = event.currentTarget;
150
+ onRun(t.value.slice(t.selectionStart, t.selectionEnd) || t.value);
151
+ }
152
+ }, className: "absolute inset-0 resize-none bg-background p-3 font-mono text-sm text-foreground outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset" }))] }));
153
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+ export interface PaletteItem {
3
+ /** Stable: the recent list and the option's key. */
4
+ id: string;
5
+ label: string;
6
+ /** The heading it sits under ("Editor", "Tables", "Open tabs"). */
7
+ group?: string;
8
+ /** A decorative icon (`aria-hidden`). */
9
+ icon?: React.ReactNode;
10
+ /** Extra words the search matches. */
11
+ keywords?: string;
12
+ /** A second, quieter line: a path, a server. */
13
+ description?: string;
14
+ /** Its chord, drawn as key caps: `"Mod+N"`. */
15
+ shortcut?: string;
16
+ disabled?: boolean;
17
+ /** Leave the palette open after it runs (an entry that switches the palette's own mode). */
18
+ keepOpen?: boolean;
19
+ }
20
+ export interface CommandPaletteProps {
21
+ open: boolean;
22
+ onOpenChange: (open: boolean) => void;
23
+ items: readonly PaletteItem[];
24
+ /** Run an entry. The palette has already closed unless the entry keeps it open. */
25
+ onSelect: (item: PaletteItem) => void;
26
+ /** The search text. Controlled when given, so a host can switch lists on a prefix ("@", ">"). */
27
+ query?: string;
28
+ onQueryChange?: (query: string) => void;
29
+ /** Rank and filter `items` by the query. False when the host already did. Defaults to true. */
30
+ filter?: boolean;
31
+ /** Ids run lately, shown first under "Recent" while the query is empty. */
32
+ recent?: readonly string[];
33
+ /** The host is still building `items` (an object index): shown under the list. */
34
+ loading?: boolean;
35
+ placeholder?: string;
36
+ /** Copy for no match. */
37
+ empty?: string;
38
+ /** A key on the highlighted entry that the palette does not handle (Delete to close a tab). */
39
+ onItemKeyDown?: (item: PaletteItem, event: React.KeyboardEvent<HTMLInputElement>) => void;
40
+ mac?: boolean;
41
+ className?: string;
42
+ }
43
+ /**
44
+ * The command palette: a modal search over commands, objects and open tabs,
45
+ * with the ARIA combobox pattern (the input owns a listbox; Up and Down move
46
+ * the highlight, Enter runs it, Escape closes). The host supplies the items
47
+ * and runs them; focus returns where it was when the palette closes.
48
+ */
49
+ export declare function CommandPalette({ open, onOpenChange, items, onSelect, query: queryProp, onQueryChange, filter, recent, loading, placeholder, empty, onItemKeyDown, mac, className, }: CommandPaletteProps): React.JSX.Element;
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { SearchIcon } from "lucide-react";
4
+ import * as React from "react";
5
+ import { cn } from "../lib/cn.js";
6
+ import { Dialog, DialogContent, DialogTitle } from "../overlays/Dialog.js";
7
+ import { useUI } from "../provider/UIProvider.js";
8
+ import { rankItems } from "./palette.js";
9
+ import { ShortcutKeys } from "./Shortcuts.js";
10
+ function sections(items, query, filter, recent, recentLabel) {
11
+ const list = filter ? rankItems(items, query) : [...items];
12
+ const out = [];
13
+ let rest = list;
14
+ if (query.trim() === "" && recent && recent.length > 0) {
15
+ const byId = new Map(list.map((i) => [i.id, i]));
16
+ const firsts = recent.flatMap((id) => byId.get(id) ?? []);
17
+ if (firsts.length > 0) {
18
+ out.push({ heading: recentLabel, items: firsts });
19
+ const taken = new Set(firsts.map((i) => i.id));
20
+ rest = list.filter((i) => !taken.has(i.id));
21
+ }
22
+ }
23
+ const groups = new Map();
24
+ for (const item of rest)
25
+ groups.set(item.group, [...(groups.get(item.group) ?? []), item]);
26
+ for (const [heading, groupItems] of groups)
27
+ out.push({ heading, items: groupItems });
28
+ return out;
29
+ }
30
+ /**
31
+ * The command palette: a modal search over commands, objects and open tabs,
32
+ * with the ARIA combobox pattern (the input owns a listbox; Up and Down move
33
+ * the highlight, Enter runs it, Escape closes). The host supplies the items
34
+ * and runs them; focus returns where it was when the palette closes.
35
+ */
36
+ export function CommandPalette({ open, onOpenChange, items, onSelect, query: queryProp, onQueryChange, filter = true, recent, loading = false, placeholder, empty, onItemKeyDown, mac, className, }) {
37
+ const { messages } = useUI();
38
+ const m = messages.workbench;
39
+ const [ownQuery, setOwnQuery] = React.useState("");
40
+ const query = queryProp ?? ownQuery;
41
+ const setQuery = (next) => {
42
+ if (queryProp === undefined)
43
+ setOwnQuery(next);
44
+ onQueryChange?.(next);
45
+ };
46
+ // The highlight belongs to one query: typing puts it back on the first match.
47
+ const [highlight, setHighlight] = React.useState({ query: "", index: 0 });
48
+ const active = highlight.query === query ? highlight.index : 0;
49
+ const setActive = (index) => setHighlight({ query, index });
50
+ const inputRef = React.useRef(null);
51
+ const listRef = React.useRef(null);
52
+ const restoreRef = React.useRef(null);
53
+ const baseId = React.useId();
54
+ const shown = sections(items, query, filter, recent, m.recent);
55
+ const flat = shown.flatMap((s) => s.items);
56
+ const current = flat[Math.min(active, flat.length - 1)];
57
+ const optionId = (index) => `${baseId}-option-${index}`;
58
+ React.useEffect(() => {
59
+ if (!open)
60
+ return;
61
+ const el = listRef.current?.querySelector('[aria-selected="true"]');
62
+ el?.scrollIntoView?.({ block: "nearest" });
63
+ }, [active, open]);
64
+ const close = () => {
65
+ onOpenChange(false);
66
+ if (queryProp === undefined)
67
+ setOwnQuery("");
68
+ };
69
+ const run = (item) => {
70
+ if (!item || item.disabled)
71
+ return;
72
+ if (!item.keepOpen)
73
+ close();
74
+ onSelect(item);
75
+ };
76
+ const move = (delta) => {
77
+ if (flat.length === 0)
78
+ return;
79
+ setActive((Math.min(active, flat.length - 1) + delta + flat.length) % flat.length);
80
+ };
81
+ const onKeyDown = (event) => {
82
+ if (event.key === "ArrowDown") {
83
+ event.preventDefault();
84
+ move(1);
85
+ }
86
+ else if (event.key === "ArrowUp") {
87
+ event.preventDefault();
88
+ move(-1);
89
+ }
90
+ else if (event.key === "Enter") {
91
+ event.preventDefault();
92
+ run(current);
93
+ }
94
+ else if (current) {
95
+ onItemKeyDown?.(current, event);
96
+ }
97
+ };
98
+ return (_jsx(Dialog, { open: open, onOpenChange: (next) => (next ? onOpenChange(true) : close()), children: _jsxs(DialogContent, { hideCloseButton: true, "aria-describedby": undefined, className: cn("top-24 max-w-xl translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-xl", className), onOpenAutoFocus: (event) => {
99
+ restoreRef.current = document.activeElement;
100
+ event.preventDefault();
101
+ inputRef.current?.focus();
102
+ }, onCloseAutoFocus: (event) => {
103
+ event.preventDefault();
104
+ if (restoreRef.current instanceof HTMLElement)
105
+ restoreRef.current.focus();
106
+ }, children: [_jsx(DialogTitle, { className: "sr-only", children: m.commandPalette }), _jsxs("div", { className: "flex items-center gap-2 border-b px-3", children: [_jsx(SearchIcon, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }), _jsx("input", { ref: inputRef, role: "combobox", "aria-expanded": flat.length > 0, "aria-controls": `${baseId}-list`, "aria-autocomplete": "list", "aria-activedescendant": current ? optionId(flat.indexOf(current)) : undefined, "aria-label": placeholder ?? m.searchCommands, placeholder: placeholder ?? m.searchCommands, className: "h-11 w-full bg-transparent text-sm outline-hidden placeholder:text-muted-foreground", value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown })] }), _jsx("div", { ref: listRef, id: `${baseId}-list`, role: "listbox", hidden: flat.length === 0, "aria-label": m.commandPalette, "aria-busy": loading || undefined, className: "max-h-[min(24rem,60vh)] overflow-y-auto p-1", children: shown.map((section, s) => {
107
+ const headingId = `${baseId}-group-${s}`;
108
+ return (_jsxs("div", { role: "group", "aria-labelledby": section.heading ? headingId : undefined, children: [section.heading && (_jsx("div", { id: headingId, role: "presentation", className: "px-2 pt-2 pb-1 text-2xs font-semibold tracking-wider text-muted-foreground uppercase", children: section.heading })), section.items.map((item) => {
109
+ const i = flat.indexOf(item);
110
+ const selected = item === current;
111
+ return (_jsxs("div", { id: optionId(i), role: "option", tabIndex: -1, "aria-selected": selected, "aria-disabled": item.disabled || undefined, className: cn("flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1.5 text-sm", selected && "bg-accent text-accent-foreground", item.disabled && "cursor-default opacity-50"), onPointerMove: () => i !== active && setActive(i), onClick: () => run(item), children: [item.icon && (_jsx("span", { "aria-hidden": true, className: "flex shrink-0 text-muted-foreground [&_svg]:size-4", children: item.icon })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate", children: item.label }), item.description && (_jsxs(_Fragment, { children: [" ", _jsx("span", { className: "block truncate text-xs text-muted-foreground", children: item.description })] }))] }), item.shortcut && _jsx(ShortcutKeys, { keys: item.shortcut, mac: mac })] }, `${section.heading ?? ""}:${item.id}`));
112
+ })] }, section.heading ?? `section-${s}`));
113
+ }) }), (loading || flat.length === 0) && (_jsx("div", { role: "status", className: "px-3 py-4 text-center text-sm text-muted-foreground", children: loading ? m.commandsLoading : (empty ?? m.noCommands) }))] }) }));
114
+ }
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+ import { type GridColumn, type GridRow } from "./grid-model.js";
3
+ /**
4
+ * The parts of Recharts the chart draws with. The host hands over the
5
+ * module (`loadCharts={() => import("recharts")}`), so the package never
6
+ * names Recharts in an import: an app without it builds, and one with it
7
+ * loads it only when a chart opens.
8
+ */
9
+ export interface ChartLibrary {
10
+ ResponsiveContainer: React.ElementType;
11
+ BarChart: React.ElementType;
12
+ Bar: React.ElementType;
13
+ LineChart: React.ElementType;
14
+ Line: React.ElementType;
15
+ AreaChart: React.ElementType;
16
+ Area: React.ElementType;
17
+ ScatterChart: React.ElementType;
18
+ Scatter: React.ElementType;
19
+ PieChart: React.ElementType;
20
+ Pie: React.ElementType;
21
+ Cell: React.ElementType;
22
+ XAxis: React.ElementType;
23
+ YAxis: React.ElementType;
24
+ CartesianGrid: React.ElementType;
25
+ Tooltip: React.ElementType;
26
+ Legend: React.ElementType;
27
+ }
28
+ export type ChartKind = "bar" | "line" | "area" | "scatter" | "pie";
29
+ export interface DataChartProps {
30
+ columns: readonly (string | GridColumn)[];
31
+ rows: readonly GridRow[];
32
+ /** Resolves to Recharts (an optional peer): `() => import("recharts")`. Called once, when the chart mounts. */
33
+ loadCharts: () => Promise<ChartLibrary>;
34
+ /** A note that the rows themselves are capped. */
35
+ truncated?: string | null;
36
+ /** Rows plotted at most. */
37
+ rowCap?: number;
38
+ className?: string;
39
+ }
40
+ /**
41
+ * A quick chart of a result: bar, line, area, scatter or pie, an X column
42
+ * and one or more Y columns, coloured by the theme's chart series. Recharts
43
+ * is the host's to supply (`loadCharts`); without it nothing is loaded.
44
+ */
45
+ export declare function DataChart(props: DataChartProps): React.JSX.Element;