@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,185 @@
1
+ /**
2
+ * Column and selection statistics, and the column profile (from Loadr's grid
3
+ * and profile view). Pure.
4
+ *
5
+ * One pass, no sorting of the whole column: at 50k rows the grid computes these
6
+ * on a header menu opening or a selection change, so the cost has to stay linear
7
+ * and allocation-light.
8
+ */
9
+ function asNumber(value) {
10
+ if (typeof value === "number")
11
+ return Number.isFinite(value) ? value : null;
12
+ if (typeof value === "string" && value.trim() !== "") {
13
+ const parsed = Number(value);
14
+ return Number.isFinite(parsed) ? parsed : null;
15
+ }
16
+ return null;
17
+ }
18
+ export function computeStats(values) {
19
+ let nulls = 0;
20
+ let numericCount = 0;
21
+ let nonNull = 0;
22
+ let sum = 0;
23
+ let numMin = Number.POSITIVE_INFINITY;
24
+ let numMax = Number.NEGATIVE_INFINITY;
25
+ let strMin;
26
+ let strMax;
27
+ const distinct = new Set();
28
+ for (const value of values) {
29
+ if (value === null || value === undefined) {
30
+ nulls++;
31
+ continue;
32
+ }
33
+ nonNull++;
34
+ const text = typeof value === "object" ? JSON.stringify(value) : String(value);
35
+ distinct.add(text);
36
+ const num = typeof value === "number" && Number.isFinite(value) ? value : null;
37
+ if (num !== null) {
38
+ numericCount++;
39
+ sum += num;
40
+ if (num < numMin)
41
+ numMin = num;
42
+ if (num > numMax)
43
+ numMax = num;
44
+ }
45
+ else {
46
+ if (strMin === undefined || text < strMin)
47
+ strMin = text;
48
+ if (strMax === undefined || text > strMax)
49
+ strMax = text;
50
+ }
51
+ }
52
+ const numeric = nonNull > 0 && numericCount === nonNull;
53
+ const stats = {
54
+ count: nulls + nonNull,
55
+ nulls,
56
+ distinct: distinct.size,
57
+ numeric,
58
+ };
59
+ if (numeric) {
60
+ stats.min = numMin;
61
+ stats.max = numMax;
62
+ stats.sum = sum;
63
+ stats.avg = sum / numericCount;
64
+ }
65
+ else {
66
+ stats.min = strMin;
67
+ stats.max = strMax;
68
+ }
69
+ return stats;
70
+ }
71
+ /**
72
+ * Sum/avg over whatever in the selection parses as a number.
73
+ *
74
+ * Unlike `computeStats` this does coerce numeric strings, because a selection
75
+ * spans columns and a numeric column arriving as text should still add up.
76
+ */
77
+ export function numericSummary(values) {
78
+ let count = 0;
79
+ let sum = 0;
80
+ let min = Number.POSITIVE_INFINITY;
81
+ let max = Number.NEGATIVE_INFINITY;
82
+ for (const value of values) {
83
+ const num = asNumber(value);
84
+ if (num === null)
85
+ continue;
86
+ count++;
87
+ sum += num;
88
+ if (num < min)
89
+ min = num;
90
+ if (num > max)
91
+ max = num;
92
+ }
93
+ if (count === 0)
94
+ return null;
95
+ return { count, sum, avg: sum / count, min, max };
96
+ }
97
+ export function formatStatValue(value) {
98
+ if (value === undefined)
99
+ return "";
100
+ if (typeof value === "number") {
101
+ return Number.isInteger(value)
102
+ ? value.toLocaleString()
103
+ : value.toLocaleString(undefined, { maximumFractionDigits: 4 });
104
+ }
105
+ return value.length > 40 ? `${value.slice(0, 40)}\u2026` : value;
106
+ }
107
+ function profileKind(values) {
108
+ let total = 0;
109
+ let nums = 0;
110
+ let bools = 0;
111
+ let dates = 0;
112
+ for (const v of values) {
113
+ if (v === null || v === undefined)
114
+ continue;
115
+ total++;
116
+ if (typeof v === "number")
117
+ nums++;
118
+ else if (typeof v === "boolean")
119
+ bools++;
120
+ else if (typeof v === "string") {
121
+ if (/^\d{4}-\d{2}-\d{2}/.test(v))
122
+ dates++;
123
+ else if (asNumber(v) !== null)
124
+ nums++;
125
+ }
126
+ }
127
+ if (total === 0)
128
+ return "null";
129
+ if (bools / total > 0.8)
130
+ return "boolean";
131
+ if (dates / total > 0.8)
132
+ return "date";
133
+ if (nums / total > 0.8)
134
+ return "numeric";
135
+ return "text";
136
+ }
137
+ /** A column's profile: counts, range, mean and median (numeric), and the top values. */
138
+ export function profileColumn(name, values, topCount = 5) {
139
+ const kind = profileKind(values);
140
+ const freq = new Map();
141
+ let nulls = 0;
142
+ for (const v of values) {
143
+ if (v === null || v === undefined) {
144
+ nulls++;
145
+ continue;
146
+ }
147
+ const text = typeof v === "object" ? JSON.stringify(v) : String(v);
148
+ freq.set(text, (freq.get(text) ?? 0) + 1);
149
+ }
150
+ const nonNull = values.length - nulls;
151
+ const profile = {
152
+ name,
153
+ kind,
154
+ count: values.length,
155
+ nulls,
156
+ distinct: freq.size,
157
+ top: [...freq.entries()]
158
+ .sort((a, b) => b[1] - a[1])
159
+ .slice(0, topCount)
160
+ .map(([value, count]) => ({ value, count, share: nonNull > 0 ? count / nonNull : 0 })),
161
+ };
162
+ if (kind === "numeric") {
163
+ const nums = [];
164
+ for (const v of values) {
165
+ const n = asNumber(v);
166
+ if (n !== null)
167
+ nums.push(n);
168
+ }
169
+ nums.sort((a, b) => a - b);
170
+ if (nums.length > 0) {
171
+ const mid = Math.floor(nums.length / 2);
172
+ profile.min = nums[0];
173
+ profile.max = nums[nums.length - 1];
174
+ profile.mean = nums.reduce((s, n) => s + n, 0) / nums.length;
175
+ profile.median =
176
+ nums.length % 2 === 0 ? ((nums[mid - 1] ?? 0) + (nums[mid] ?? 0)) / 2 : nums[mid];
177
+ }
178
+ }
179
+ else if (kind === "text" || kind === "date") {
180
+ const keys = [...freq.keys()].sort();
181
+ profile.min = keys[0];
182
+ profile.max = keys[keys.length - 1];
183
+ }
184
+ return profile;
185
+ }
@@ -14,3 +14,31 @@ export { FileGrid, type FileGridProps } from "./FileGrid.js";
14
14
  export { FileBrowser, type FileBrowserProps } from "./FileBrowser.js";
15
15
  export { UploadDropzone, type UploadDropzoneProps, type UploadItem, type UploadRejection, } from "./UploadDropzone.js";
16
16
  export { FilePreview, type FilePreviewContent, type FilePreviewProps } from "./FilePreview.js";
17
+ export { formatShortcut, isMacPlatform, matchesShortcut, parseShortcut, shortcutKeys, type KeyChord, } from "./keys.js";
18
+ export { KeyboardShortcutsDialog, ShortcutKeys, useRegisteredShortcuts, useShortcuts, type KeyboardShortcutsDialogProps, type ShortcutDefinition, type ShortcutEntry, type ShortcutKeysProps, } from "./Shortcuts.js";
19
+ export { fuzzyScore, rankItems, type PaletteMatchable } from "./palette.js";
20
+ export { CommandPalette, type CommandPaletteProps, type PaletteItem } from "./CommandPalette.js";
21
+ export { MenuBar, type MenuBarProps, type MenuEntry, type WorkbenchMenu } from "./MenuBar.js";
22
+ export { ActivityBar, type ActivityBarProps, type ActivityItem } from "./ActivityBar.js";
23
+ export { EditorGroup, EditorTabs, editorTabId, type EditorGroupProps, type EditorTab, type EditorTabsProps, } from "./EditorTabs.js";
24
+ export { Pane, PaneGroup, resolvePaneSizes, type PaneGroupProps, type PaneProps, type PaneSize, } from "./PaneGroup.js";
25
+ export { BottomPanel, type BottomPanelProps, type BottomPanelView } from "./BottomPanel.js";
26
+ export { StatusBar, StatusBarItem, type StatusBarItemProps, type StatusBarProps, type StatusBarTone, } from "./StatusBar.js";
27
+ export { WorkbenchShell, type WorkbenchShellProps } from "./WorkbenchShell.js";
28
+ export { COLUMN_TYPES, asBinary, buildColumnMeta, cellText, columnSignature, compareCells, isNumericType, normalizeColumnType, sniffColumnType, viewRowIndexes, type BinaryCell, type ColumnType, type GridColumn, type GridColumnMeta, type GridRow, type GridSort, type SortDirection, } from "./grid-model.js";
29
+ export { EMPTY_SELECTION, extendSelection, isSelected, moveActive, selectAll, selectCell, selectColumn, selectRow, selectionBounds, selectionSize, toggleCell, type CellRef, type SelectionRange, type SelectionState, } from "./grid-selection.js";
30
+ export { EXPORT_FORMATS, exportBlob, exportMatrix, formatMatrix, quoteIdentifier, rowsMatrix, selectionMatrix, sqlLiteral, toCSV, toInsert, toJSON, toJSONL, toMarkdown, toTSV, type CellMatrix, type CopyFormat, type ExportFile, type ExportFormat, } from "./grid-format.js";
31
+ export { computeStats, formatStatValue, numericSummary, profileColumn, type ColumnProfile, type ColumnStats, type NumericSummary, type ProfileKind, } from "./grid-stats.js";
32
+ export { NO_CHANGES, addRow, changeSetToSql, deleteRows, editCell, insertedKeys, isCellChanged, parseCellInput, pendingCounts, pendingValue, revertRow, toChangeSet, type ChangeSet, type ChangeSetDelete, type ChangeSetInsert, type ChangeSetUpdate, type PendingChanges, type PendingCounts, type PendingRow, type PendingState, } from "./pending.js";
33
+ export { downloadBase64, downloadBlob, downloadExport } from "./download.js";
34
+ export { ColumnKeyIcons, ColumnTypeIcon, GridCellValue, type ColumnKeyIconsProps, type ColumnTypeIconProps, type GridCellValueProps, } from "./GridCell.js";
35
+ export { TruncationBanner, type TruncationBannerProps } from "./TruncationBanner.js";
36
+ export { CellDetailDialog, RowDetailDrawer, type CellDetailDialogProps, type RowDetailDrawerProps, } from "./GridDetails.js";
37
+ export { PendingChangesBar, type PendingChangesBarProps } from "./PendingChangesBar.js";
38
+ export { DataGrid, type DataGridProps, type GridCommitError, type GridCopyEvent, type GridExportOption, type GridExportRequest, } from "./DataGrid.js";
39
+ export { DataProfile, type DataProfileProps } from "./DataProfile.js";
40
+ export { DataChart, type ChartKind, type ChartLibrary, type DataChartProps } from "./DataChart.js";
41
+ export { CodeEditor, configureMonaco, type CodeEditorProps, type MonacoApi, type MonacoEditor, } from "./CodeEditor.js";
42
+ export { SqlEditor, type SqlEditorProps } from "./SqlEditor.js";
43
+ export { ambiguousColumns, extractTableRefs, isDdlStatement, resolveSqlDot, sqlCompletions, sqlCursorContext, sqlMatch, statementAt, SQL_FUNCTIONS, SQL_KEYWORDS, type SqlColumn, type SqlCompletion, type SqlCompletionKind, type SqlCompletionResult, type SqlCursorContext, type SqlDotTarget, type SqlSchema, type SqlSchemaRequest, type SqlTable, type SqlTableRef, } from "./sql-completion.js";
44
+ export { cssColorToHex, editorTheme, readEditorPalette, type EditorPalette, type EditorThemeData, } from "./editor-theme.js";
@@ -14,3 +14,34 @@ export { FileGrid } from "./FileGrid.js";
14
14
  export { FileBrowser } from "./FileBrowser.js";
15
15
  export { UploadDropzone, } from "./UploadDropzone.js";
16
16
  export { FilePreview } from "./FilePreview.js";
17
+ // The IDE shell.
18
+ export { formatShortcut, isMacPlatform, matchesShortcut, parseShortcut, shortcutKeys, } from "./keys.js";
19
+ export { KeyboardShortcutsDialog, ShortcutKeys, useRegisteredShortcuts, useShortcuts, } from "./Shortcuts.js";
20
+ export { fuzzyScore, rankItems } from "./palette.js";
21
+ export { CommandPalette } from "./CommandPalette.js";
22
+ export { MenuBar } from "./MenuBar.js";
23
+ export { ActivityBar } from "./ActivityBar.js";
24
+ export { EditorGroup, EditorTabs, editorTabId, } from "./EditorTabs.js";
25
+ export { Pane, PaneGroup, resolvePaneSizes, } from "./PaneGroup.js";
26
+ export { BottomPanel } from "./BottomPanel.js";
27
+ export { StatusBar, StatusBarItem, } from "./StatusBar.js";
28
+ export { WorkbenchShell } from "./WorkbenchShell.js";
29
+ // The data grid, profile and chart.
30
+ export { COLUMN_TYPES, asBinary, buildColumnMeta, cellText, columnSignature, compareCells, isNumericType, normalizeColumnType, sniffColumnType, viewRowIndexes, } from "./grid-model.js";
31
+ export { EMPTY_SELECTION, extendSelection, isSelected, moveActive, selectAll, selectCell, selectColumn, selectRow, selectionBounds, selectionSize, toggleCell, } from "./grid-selection.js";
32
+ export { EXPORT_FORMATS, exportBlob, exportMatrix, formatMatrix, quoteIdentifier, rowsMatrix, selectionMatrix, sqlLiteral, toCSV, toInsert, toJSON, toJSONL, toMarkdown, toTSV, } from "./grid-format.js";
33
+ export { computeStats, formatStatValue, numericSummary, profileColumn, } from "./grid-stats.js";
34
+ export { NO_CHANGES, addRow, changeSetToSql, deleteRows, editCell, insertedKeys, isCellChanged, parseCellInput, pendingCounts, pendingValue, revertRow, toChangeSet, } from "./pending.js";
35
+ export { downloadBase64, downloadBlob, downloadExport } from "./download.js";
36
+ export { ColumnKeyIcons, ColumnTypeIcon, GridCellValue, } from "./GridCell.js";
37
+ export { TruncationBanner } from "./TruncationBanner.js";
38
+ export { CellDetailDialog, RowDetailDrawer, } from "./GridDetails.js";
39
+ export { PendingChangesBar } from "./PendingChangesBar.js";
40
+ export { DataGrid, } from "./DataGrid.js";
41
+ export { DataProfile } from "./DataProfile.js";
42
+ export { DataChart } from "./DataChart.js";
43
+ // The code and SQL editors (Monaco, an optional peer the host loads).
44
+ export { CodeEditor, configureMonaco, } from "./CodeEditor.js";
45
+ export { SqlEditor } from "./SqlEditor.js";
46
+ export { ambiguousColumns, extractTableRefs, isDdlStatement, resolveSqlDot, sqlCompletions, sqlCursorContext, sqlMatch, statementAt, SQL_FUNCTIONS, SQL_KEYWORDS, } from "./sql-completion.js";
47
+ export { cssColorToHex, editorTheme, readEditorPalette, } from "./editor-theme.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Keyboard chords as strings: `"Mod+K"`, `"Mod+Shift+A"`, `"Alt+ArrowLeft"`,
3
+ * `"F2"`. `Mod` is Command on a Mac and Control elsewhere, so one binding
4
+ * serves both. Pure: parsing, matching and display, no listeners.
5
+ */
6
+ export interface KeyChord {
7
+ /** The key, as `KeyboardEvent.key` names it, lowercased for letters. */
8
+ key: string;
9
+ mod: boolean;
10
+ ctrl: boolean;
11
+ meta: boolean;
12
+ alt: boolean;
13
+ shift: boolean;
14
+ }
15
+ export declare function parseShortcut(shortcut: string): KeyChord;
16
+ export declare function isMacPlatform(): boolean;
17
+ type KeyEventLike = Pick<KeyboardEvent, "key" | "code" | "ctrlKey" | "metaKey" | "altKey" | "shiftKey">;
18
+ export declare function matchesShortcut(event: KeyEventLike, shortcut: string | KeyChord, isMac?: boolean): boolean;
19
+ /** The keys of a chord as they are drawn: `["⌘", "K"]` on a Mac, `["Ctrl", "K"]` elsewhere. */
20
+ export declare function shortcutKeys(shortcut: string, isMac?: boolean): string[];
21
+ /** One string for a menu or a tooltip: `"⌘K"` on a Mac, `"Ctrl+K"` elsewhere. */
22
+ export declare function formatShortcut(shortcut: string, isMac?: boolean): string;
23
+ export {};
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Keyboard chords as strings: `"Mod+K"`, `"Mod+Shift+A"`, `"Alt+ArrowLeft"`,
3
+ * `"F2"`. `Mod` is Command on a Mac and Control elsewhere, so one binding
4
+ * serves both. Pure: parsing, matching and display, no listeners.
5
+ */
6
+ const MODIFIERS = new Set(["mod", "ctrl", "control", "meta", "cmd", "alt", "option", "shift"]);
7
+ export function parseShortcut(shortcut) {
8
+ const chord = {
9
+ key: "",
10
+ mod: false,
11
+ ctrl: false,
12
+ meta: false,
13
+ alt: false,
14
+ shift: false,
15
+ };
16
+ // "Mod++" binds the plus key: the last part is the key, even when empty.
17
+ const plus = shortcut === "+" || shortcut.endsWith("++");
18
+ const parts = (plus ? shortcut.slice(0, -1) : shortcut).split("+");
19
+ const last = parts.pop() ?? "";
20
+ const raw = plus ? "+" : last;
21
+ for (const part of parts) {
22
+ const p = part.trim().toLowerCase();
23
+ if (!MODIFIERS.has(p))
24
+ throw new Error(`Unknown modifier "${part}" in "${shortcut}"`);
25
+ if (p === "mod")
26
+ chord.mod = true;
27
+ else if (p === "ctrl" || p === "control")
28
+ chord.ctrl = true;
29
+ else if (p === "meta" || p === "cmd")
30
+ chord.meta = true;
31
+ else if (p === "alt" || p === "option")
32
+ chord.alt = true;
33
+ else
34
+ chord.shift = true;
35
+ }
36
+ const key = raw.trim() || raw;
37
+ chord.key = key.length === 1 ? key.toLowerCase() : key;
38
+ return chord;
39
+ }
40
+ export function isMacPlatform() {
41
+ if (typeof navigator === "undefined")
42
+ return false;
43
+ const nav = navigator;
44
+ return /mac|iphone|ipad/i.test(nav.userAgentData?.platform ?? nav.platform ?? "");
45
+ }
46
+ /** The key a physical code stands for, so Alt and Shift layouts still match letters and digits. */
47
+ function keyFromCode(code) {
48
+ const m = /^(?:Key([A-Z])|Digit(\d))$/.exec(code);
49
+ if (!m)
50
+ return null;
51
+ return (m[1] ?? m[2] ?? "").toLowerCase();
52
+ }
53
+ export function matchesShortcut(event, shortcut, isMac = isMacPlatform()) {
54
+ const chord = typeof shortcut === "string" ? parseShortcut(shortcut) : shortcut;
55
+ const wantCtrl = chord.ctrl || (chord.mod && !isMac);
56
+ const wantMeta = chord.meta || (chord.mod && isMac);
57
+ if (event.ctrlKey !== wantCtrl || event.metaKey !== wantMeta || event.altKey !== chord.alt) {
58
+ return false;
59
+ }
60
+ const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
61
+ // A shifted symbol ("?" for Shift+/) carries its shift in the key itself.
62
+ if (event.shiftKey !== chord.shift &&
63
+ !(key === chord.key && key.length === 1 && !/[a-z0-9]/.test(key))) {
64
+ return false;
65
+ }
66
+ return key === chord.key || keyFromCode(event.code) === chord.key;
67
+ }
68
+ const MAC_GLYPH = {
69
+ mod: "⌘",
70
+ meta: "⌘",
71
+ ctrl: "⌃",
72
+ alt: "⌥",
73
+ shift: "⇧",
74
+ };
75
+ const KEY_NAME = {
76
+ ArrowUp: "↑",
77
+ ArrowDown: "↓",
78
+ ArrowLeft: "←",
79
+ ArrowRight: "→",
80
+ Enter: "Enter",
81
+ Escape: "Esc",
82
+ Backspace: "Backspace",
83
+ Delete: "Del",
84
+ " ": "Space",
85
+ };
86
+ /** The keys of a chord as they are drawn: `["⌘", "K"]` on a Mac, `["Ctrl", "K"]` elsewhere. */
87
+ export function shortcutKeys(shortcut, isMac = isMacPlatform()) {
88
+ const chord = parseShortcut(shortcut);
89
+ const keys = [];
90
+ const add = (name, label) => keys.push(isMac ? (MAC_GLYPH[name] ?? label) : label);
91
+ if (chord.ctrl || (chord.mod && !isMac))
92
+ add("ctrl", "Ctrl");
93
+ if (chord.alt)
94
+ add("alt", "Alt");
95
+ if (chord.shift)
96
+ add("shift", "Shift");
97
+ if (chord.meta || (chord.mod && isMac))
98
+ add("meta", "Meta");
99
+ keys.push(KEY_NAME[chord.key] ?? (chord.key.length === 1 ? chord.key.toUpperCase() : chord.key));
100
+ return keys;
101
+ }
102
+ /** One string for a menu or a tooltip: `"⌘K"` on a Mac, `"Ctrl+K"` elsewhere. */
103
+ export function formatShortcut(shortcut, isMac = isMacPlatform()) {
104
+ return shortcutKeys(shortcut, isMac).join(isMac ? "" : "+");
105
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The command palette's matching: pure, so a host can rank its own lists
3
+ * with the same rules the palette uses.
4
+ */
5
+ /**
6
+ * How well `query` matches `text` as a subsequence, higher is better, or -1
7
+ * when it does not match. Consecutive characters, word starts and an exact
8
+ * prefix score more, so "nq" ranks "New Query" above "Run query".
9
+ */
10
+ export declare function fuzzyScore(text: string, query: string): number;
11
+ export interface PaletteMatchable {
12
+ label: string;
13
+ keywords?: string;
14
+ group?: string;
15
+ }
16
+ /** The items that match, best first; ties keep the given order. */
17
+ export declare function rankItems<T extends PaletteMatchable>(items: readonly T[], query: string): T[];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The command palette's matching: pure, so a host can rank its own lists
3
+ * with the same rules the palette uses.
4
+ */
5
+ /**
6
+ * How well `query` matches `text` as a subsequence, higher is better, or -1
7
+ * when it does not match. Consecutive characters, word starts and an exact
8
+ * prefix score more, so "nq" ranks "New Query" above "Run query".
9
+ */
10
+ export function fuzzyScore(text, query) {
11
+ const q = query.trim().toLowerCase();
12
+ if (q === "")
13
+ return 0;
14
+ const t = text.toLowerCase();
15
+ if (t.startsWith(q))
16
+ return 1000 - t.length;
17
+ let score = 0;
18
+ let ti = 0;
19
+ let streak = 0;
20
+ for (const ch of q) {
21
+ if (ch === " ")
22
+ continue;
23
+ const found = t.indexOf(ch, ti);
24
+ if (found === -1)
25
+ return -1;
26
+ const wordStart = found === 0 || /[\s\-_./:]/.test(t[found - 1] ?? "");
27
+ streak = found === ti ? streak + 1 : 0;
28
+ score += 1 + streak * 3 + (wordStart ? 8 : 0) - Math.min(found - ti, 10) * 0.1;
29
+ ti = found + 1;
30
+ }
31
+ return score;
32
+ }
33
+ /** The items that match, best first; ties keep the given order. */
34
+ export function rankItems(items, query) {
35
+ if (query.trim() === "")
36
+ return [...items];
37
+ return items
38
+ .map((item, index) => {
39
+ const score = Math.max(fuzzyScore(item.label, query), item.keywords ? fuzzyScore(item.keywords, query) - 1 : -1);
40
+ return { item, index, score };
41
+ })
42
+ .filter((r) => r.score >= 0)
43
+ .sort((a, b) => b.score - a.score || a.index - b.index)
44
+ .map((r) => r.item);
45
+ }
@@ -0,0 +1,92 @@
1
+ import { type ColumnType, type GridRow } from "./grid-model.js";
2
+ export type PendingState = "modified" | "added" | "deleted";
3
+ /** One row that differs from what the host holds. */
4
+ export interface PendingRow {
5
+ state: PendingState;
6
+ /** The row as it was read; empty for an inserted row. */
7
+ original: GridRow;
8
+ /** The row as it will be written. */
9
+ values: GridRow;
10
+ }
11
+ export interface PendingChanges {
12
+ readonly rows: ReadonlyMap<number, PendingRow>;
13
+ /** Rows inserted so far, for the next insert's key. */
14
+ readonly inserted: number;
15
+ }
16
+ export declare const NO_CHANGES: PendingChanges;
17
+ /**
18
+ * Sets one cell. `source` is the row as read (unused for an inserted row).
19
+ * An edit back to the original value drops the row's entry.
20
+ */
21
+ export declare function editCell(changes: PendingChanges, key: number, column: string, value: unknown, source: GridRow | undefined): PendingChanges;
22
+ /** Inserts a row of NULLs, or a copy of `from` (duplicate). Returns the new row's key. */
23
+ export declare function addRow(changes: PendingChanges, columns: readonly string[], from?: GridRow): {
24
+ changes: PendingChanges;
25
+ key: number;
26
+ };
27
+ /** Marks rows deleted; an inserted row is simply dropped. */
28
+ export declare function deleteRows(changes: PendingChanges, keys: Iterable<number>, sourceOf: (key: number) => GridRow | undefined): PendingChanges;
29
+ /** Forgets every change to one row (an inserted row disappears). */
30
+ export declare function revertRow(changes: PendingChanges, key: number): PendingChanges;
31
+ /** The value a cell shows: the pending value when there is one. */
32
+ export declare function pendingValue(changes: PendingChanges, key: number, column: string, source: GridRow | undefined): unknown;
33
+ /** True when the cell will be written: every cell of an inserted row, the edited cells of a modified one. */
34
+ export declare function isCellChanged(changes: PendingChanges, key: number, column: string): boolean;
35
+ /** The keys of the inserted rows, in the order they were added. */
36
+ export declare function insertedKeys(changes: PendingChanges): number[];
37
+ export interface PendingCounts {
38
+ inserts: number;
39
+ updates: number;
40
+ deletes: number;
41
+ total: number;
42
+ }
43
+ export declare function pendingCounts(changes: PendingChanges): PendingCounts;
44
+ /** One statement's worth of a commit. `key` is the grid's row key, for reporting a failure back. */
45
+ export interface ChangeSetInsert {
46
+ key: number;
47
+ /** The cells the person filled; an untouched NULL is left to the column's default. */
48
+ values: Record<string, unknown>;
49
+ }
50
+ export interface ChangeSetUpdate {
51
+ key: number;
52
+ /** The primary key values the row was read under. */
53
+ primaryKey: Record<string, unknown>;
54
+ /** The changed cells only. */
55
+ values: Record<string, unknown>;
56
+ /** The old values of exactly those cells, for an optimistic-concurrency check (JSON and binary cells left out). */
57
+ expected: Record<string, unknown>;
58
+ }
59
+ export interface ChangeSetDelete {
60
+ key: number;
61
+ primaryKey: Record<string, unknown>;
62
+ }
63
+ /** What a commit hands the host. */
64
+ export interface ChangeSet {
65
+ inserts: ChangeSetInsert[];
66
+ updates: ChangeSetUpdate[];
67
+ deletes: ChangeSetDelete[];
68
+ }
69
+ /**
70
+ * The pending changes as a batch to write. Updates and deletes are keyed on
71
+ * `primaryKeys`; without one they cannot name a row, and the grid only
72
+ * offers inserts.
73
+ */
74
+ export declare function toChangeSet(changes: PendingChanges, options: {
75
+ columns: readonly string[];
76
+ primaryKeys: readonly string[];
77
+ }): ChangeSet;
78
+ /**
79
+ * A change set as SQL statements, for a host that runs SQL rather than an
80
+ * apply endpoint: INSERT, UPDATE ... WHERE <primary key>, DELETE ... WHERE
81
+ * <primary key>. Literals are quoted by `sqlLiteral`; dialect differences
82
+ * beyond ANSI quoting are the host's.
83
+ */
84
+ export declare function changeSetToSql(changeSet: ChangeSet, options: {
85
+ table: string;
86
+ }): string[];
87
+ /**
88
+ * What a typed cell means: empty or "NULL" is NULL; a number or boolean
89
+ * column (by its type, or the original value's) keeps its type when the
90
+ * text parses as one.
91
+ */
92
+ export declare function parseCellInput(text: string, original: unknown, type?: ColumnType): unknown;