@arbidocs/blocks 0.4.73 → 0.4.75

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 (104) hide show
  1. package/README.md +2 -1
  2. package/dist/agents/index.cjs +255 -254
  3. package/dist/agents/index.js +5 -4
  4. package/dist/agents/workflows/WorkflowRunner.d.ts +20 -4
  5. package/dist/agents/workflows/WorkflowStepBar.d.ts +23 -0
  6. package/dist/agents/workflows/run.d.ts +1 -0
  7. package/dist/agents/workflows/types.d.ts +2 -0
  8. package/dist/chat/index.cjs +124 -124
  9. package/dist/chat/index.js +3 -3
  10. package/dist/{chunk-3SFFTSOI.cjs → chunk-7CVF3DK2.cjs} +5 -5
  11. package/dist/{chunk-3SFFTSOI.cjs.map → chunk-7CVF3DK2.cjs.map} +1 -1
  12. package/dist/{chunk-V3L5DONY.cjs → chunk-B4SRYJLM.cjs} +31 -2
  13. package/dist/chunk-B4SRYJLM.cjs.map +1 -0
  14. package/dist/{chunk-IUR565UW.cjs → chunk-F4LUD3YC.cjs} +49 -48
  15. package/dist/chunk-F4LUD3YC.cjs.map +1 -0
  16. package/dist/{chunk-WOYUXVVQ.cjs → chunk-HITAN7JF.cjs} +4 -4
  17. package/dist/{chunk-WOYUXVVQ.cjs.map → chunk-HITAN7JF.cjs.map} +1 -1
  18. package/dist/{chunk-3VQYJBOI.js → chunk-HOXKR26S.js} +31 -4
  19. package/dist/chunk-HOXKR26S.js.map +1 -0
  20. package/dist/{chunk-KJ3QEKOJ.cjs → chunk-IPGKTUF7.cjs} +7 -33
  21. package/dist/chunk-IPGKTUF7.cjs.map +1 -0
  22. package/dist/{chunk-IA7RTPT3.cjs → chunk-IXALYQT7.cjs} +2 -200
  23. package/dist/chunk-IXALYQT7.cjs.map +1 -0
  24. package/dist/{chunk-QDNXEB3T.js → chunk-LT2UORRG.js} +7 -6
  25. package/dist/chunk-LT2UORRG.js.map +1 -0
  26. package/dist/{chunk-ORA2XE7U.js → chunk-MK6NJQGF.js} +3 -3
  27. package/dist/{chunk-ORA2XE7U.js.map → chunk-MK6NJQGF.js.map} +1 -1
  28. package/dist/chunk-NUNKSZKU.js +203 -0
  29. package/dist/chunk-NUNKSZKU.js.map +1 -0
  30. package/dist/{chunk-6OL5S7MJ.cjs → chunk-NYBIKDWI.cjs} +5 -2
  31. package/dist/chunk-NYBIKDWI.cjs.map +1 -0
  32. package/dist/{chunk-GTNUQBXI.cjs → chunk-P2RBE3FD.cjs} +254 -102
  33. package/dist/chunk-P2RBE3FD.cjs.map +1 -0
  34. package/dist/{chunk-D46ZAPEE.js → chunk-R3PDNQYT.js} +5 -2
  35. package/dist/chunk-R3PDNQYT.js.map +1 -0
  36. package/dist/{chunk-G5DP27DU.js → chunk-UIWLDSTF.js} +3 -29
  37. package/dist/chunk-UIWLDSTF.js.map +1 -0
  38. package/dist/chunk-V4ISCZJ7.cjs +207 -0
  39. package/dist/chunk-V4ISCZJ7.cjs.map +1 -0
  40. package/dist/{chunk-VRPRAU3J.js → chunk-WJEKRSPH.js} +3 -3
  41. package/dist/{chunk-VRPRAU3J.js.map → chunk-WJEKRSPH.js.map} +1 -1
  42. package/dist/{chunk-2P3T23C7.js → chunk-YWYVIQ7T.js} +256 -104
  43. package/dist/chunk-YWYVIQ7T.js.map +1 -0
  44. package/dist/{chunk-BUHKUKKB.js → chunk-ZPLG7QYF.js} +4 -199
  45. package/dist/chunk-ZPLG7QYF.js.map +1 -0
  46. package/dist/connect/index.cjs +96 -37
  47. package/dist/connect/index.cjs.map +1 -1
  48. package/dist/connect/index.js +81 -22
  49. package/dist/connect/index.js.map +1 -1
  50. package/dist/connection/ConnectedWorkflowRunner.d.ts +5 -1
  51. package/dist/connection/useWorkflowThread.d.ts +4 -0
  52. package/dist/databases/DatabaseColumnDialog.d.ts +43 -0
  53. package/dist/databases/DatabaseList.d.ts +28 -0
  54. package/dist/databases/DatabaseTable.d.ts +38 -0
  55. package/dist/databases/DatabaseToolbar.d.ts +38 -0
  56. package/dist/databases/NewDatabaseDialog.d.ts +25 -0
  57. package/dist/databases/SelectCellEditor.d.ts +6 -0
  58. package/dist/databases/SelectChip.d.ts +4 -0
  59. package/dist/databases/column-types.d.ts +58 -0
  60. package/dist/databases/databaseColumns.d.ts +28 -0
  61. package/dist/databases/index.cjs +1081 -0
  62. package/dist/databases/index.cjs.map +1 -0
  63. package/dist/databases/index.d.ts +13 -0
  64. package/dist/databases/index.js +1055 -0
  65. package/dist/databases/index.js.map +1 -0
  66. package/dist/facts/index.cjs +7 -6
  67. package/dist/facts/index.cjs.map +1 -1
  68. package/dist/facts/index.js +3 -2
  69. package/dist/facts/index.js.map +1 -1
  70. package/dist/files/index.cjs +14 -13
  71. package/dist/files/index.cjs.map +1 -1
  72. package/dist/files/index.js +4 -2
  73. package/dist/files/index.js.map +1 -1
  74. package/dist/index.cjs +632 -631
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.js +12 -11
  77. package/dist/index.js.map +1 -1
  78. package/dist/settings/index.cjs +1 -1
  79. package/dist/settings/index.js +1 -1
  80. package/dist/shell/ActionCluster.d.ts +3 -1
  81. package/dist/shell/index.cjs +45 -45
  82. package/dist/shell/index.js +1 -1
  83. package/dist/studio/index.cjs +4 -4
  84. package/dist/studio/index.js +3 -3
  85. package/dist/workflow-canvas/StepInspector.d.ts +11 -0
  86. package/dist/workflow-canvas/WorkflowCanvas.d.ts +6 -2
  87. package/dist/workflow-canvas/index.cjs +202 -91
  88. package/dist/workflow-canvas/index.cjs.map +1 -1
  89. package/dist/workflow-canvas/index.js +170 -59
  90. package/dist/workflow-canvas/index.js.map +1 -1
  91. package/dist/workflow-canvas/viewport.d.ts +47 -0
  92. package/package.json +9 -4
  93. package/dist/chunk-2P3T23C7.js.map +0 -1
  94. package/dist/chunk-3VQYJBOI.js.map +0 -1
  95. package/dist/chunk-6OL5S7MJ.cjs.map +0 -1
  96. package/dist/chunk-BUHKUKKB.js.map +0 -1
  97. package/dist/chunk-D46ZAPEE.js.map +0 -1
  98. package/dist/chunk-G5DP27DU.js.map +0 -1
  99. package/dist/chunk-GTNUQBXI.cjs.map +0 -1
  100. package/dist/chunk-IA7RTPT3.cjs.map +0 -1
  101. package/dist/chunk-IUR565UW.cjs.map +0 -1
  102. package/dist/chunk-KJ3QEKOJ.cjs.map +0 -1
  103. package/dist/chunk-QDNXEB3T.js.map +0 -1
  104. package/dist/chunk-V3L5DONY.cjs.map +0 -1
@@ -0,0 +1,1081 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunkIXALYQT7_cjs = require('../chunk-IXALYQT7.cjs');
5
+ require('../chunk-FX3K6LNZ.cjs');
6
+ var chunkMHVNT4J3_cjs = require('../chunk-MHVNT4J3.cjs');
7
+ require('../chunk-DES3K4SD.cjs');
8
+ var lucideReact = require('lucide-react');
9
+ var ui = require('@arbidocs/react/ui');
10
+ var jsxRuntime = require('react/jsx-runtime');
11
+ var react = require('react');
12
+
13
+ // src/databases/column-types.ts
14
+ var DATABASE_COLUMN_TYPES = [
15
+ "text",
16
+ "number",
17
+ "date",
18
+ "checkbox",
19
+ "select"
20
+ ];
21
+ var DEFAULT_DATABASE_COLUMN_TYPE_LABELS = {
22
+ text: "Text",
23
+ number: "Number",
24
+ date: "Date",
25
+ checkbox: "Checkbox",
26
+ select: "Select"
27
+ };
28
+ var OPTION_TONES = [
29
+ "bg-blue-bg text-blue-text",
30
+ "bg-green-bg text-green-text",
31
+ "bg-yellow-bg text-yellow-text",
32
+ "bg-purple-bg text-purple-text",
33
+ "bg-red-bg text-red-text",
34
+ "bg-gray-bg text-gray-text"
35
+ ];
36
+ function optionTone(option) {
37
+ let hash = 0;
38
+ for (const ch of option.toLowerCase()) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
39
+ return OPTION_TONES[hash % OPTION_TONES.length];
40
+ }
41
+ var isEmpty = (value) => value === null || value === void 0 || typeof value === "string" && value.trim() === "";
42
+ function formatDateValue(value, locale) {
43
+ const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
44
+ if (day) {
45
+ const [, y, m, d] = day;
46
+ return new Date(Date.UTC(+y, +m - 1, +d)).toLocaleDateString(locale, { timeZone: "UTC" });
47
+ }
48
+ const at = new Date(value);
49
+ return Number.isNaN(at.getTime()) ? value : at.toLocaleString(locale);
50
+ }
51
+ function formatNumberValue(value, locale) {
52
+ return value.toLocaleString(locale, { maximumFractionDigits: 6 });
53
+ }
54
+ function formatCell(value, type, locale) {
55
+ if (isEmpty(value)) return "";
56
+ if (type === "number" && typeof value === "number") return formatNumberValue(value, locale);
57
+ if (type === "date" && typeof value === "string") return formatDateValue(value, locale);
58
+ if (type === "checkbox") return value ? "Yes" : "No";
59
+ return String(value);
60
+ }
61
+ function toApiValue(value, type) {
62
+ if (isEmpty(value)) return null;
63
+ if (type === "checkbox") return Boolean(value);
64
+ if (type === "date" && value instanceof Date) {
65
+ const pad = (n) => String(n).padStart(2, "0");
66
+ return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
67
+ }
68
+ if (type === "number" && typeof value === "string") {
69
+ const n = Number(value.replace(/,/g, ""));
70
+ return Number.isFinite(n) ? n : value;
71
+ }
72
+ return value;
73
+ }
74
+ function compareCells(a, b, type) {
75
+ const ea = isEmpty(a);
76
+ const eb = isEmpty(b);
77
+ if (ea || eb) return ea === eb ? 0 : ea ? 1 : -1;
78
+ if (type === "number" || type === "checkbox") return Number(a) - Number(b);
79
+ return String(a).localeCompare(String(b), void 0, { sensitivity: "base", numeric: true });
80
+ }
81
+ function SelectChip({ value }) {
82
+ return /* @__PURE__ */ jsxRuntime.jsx(
83
+ "span",
84
+ {
85
+ className: ui.cn(
86
+ "inline-flex max-w-full items-center truncate rounded-md px-2 py-0.5 text-xs font-medium",
87
+ optionTone(value)
88
+ ),
89
+ children: value
90
+ }
91
+ );
92
+ }
93
+ function SelectCellEditor(props) {
94
+ const { value, onValueChange, stopEditing, options, clearLabel = "Clear" } = props;
95
+ const items = [...options, null];
96
+ const [active, setActive] = react.useState(() => Math.max(0, options.indexOf(String(value ?? ""))));
97
+ const list = react.useRef(null);
98
+ react.useEffect(() => list.current?.focus(), []);
99
+ const pick = (option) => {
100
+ onValueChange(option);
101
+ setTimeout(() => stopEditing(), 0);
102
+ };
103
+ const onKeyDown = (e) => {
104
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
105
+ e.preventDefault();
106
+ e.stopPropagation();
107
+ const step = e.key === "ArrowDown" ? 1 : -1;
108
+ setActive((i) => (i + step + items.length) % items.length);
109
+ } else if (e.key === "Enter") {
110
+ e.preventDefault();
111
+ e.stopPropagation();
112
+ pick(items[active]);
113
+ }
114
+ };
115
+ return /* @__PURE__ */ jsxRuntime.jsx(
116
+ "div",
117
+ {
118
+ ref: list,
119
+ role: "listbox",
120
+ tabIndex: -1,
121
+ onKeyDown,
122
+ "data-testid": "database-select-editor",
123
+ className: "flex min-w-44 flex-col gap-0.5 rounded-md border border-border bg-popover p-1 shadow-md outline-none",
124
+ children: items.map((option, i) => /* @__PURE__ */ jsxRuntime.jsxs(
125
+ "button",
126
+ {
127
+ type: "button",
128
+ role: "option",
129
+ "aria-selected": option === value,
130
+ onMouseEnter: () => setActive(i),
131
+ onClick: () => pick(option),
132
+ "data-testid": option === null ? "database-select-clear" : `database-select-option-${option}`,
133
+ className: ui.cn(
134
+ "flex items-center gap-2 rounded px-2 py-1 text-left text-sm",
135
+ i === active && "bg-muted"
136
+ ),
137
+ children: [
138
+ option === null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: clearLabel }) : /* @__PURE__ */ jsxRuntime.jsx(SelectChip, { value: option }),
139
+ option !== null && option === value && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "ml-auto size-3.5" })
140
+ ]
141
+ },
142
+ option ?? "__clear"
143
+ ))
144
+ }
145
+ );
146
+ }
147
+ var DEFAULT_DATABASE_COLUMN_LABELS = {
148
+ addColumn: "Add a column",
149
+ columnSettings: (name) => `${name} column settings`
150
+ };
151
+ var ADD_COLUMN_COL_ID = "__add_column";
152
+ var COLUMN_TYPE_ICONS = {
153
+ text: lucideReact.Type,
154
+ number: lucideReact.Hash,
155
+ date: lucideReact.CalendarDays,
156
+ checkbox: lucideReact.CheckSquare,
157
+ select: lucideReact.ChevronDown
158
+ };
159
+ var WIDTHS = {
160
+ text: 220,
161
+ number: 130,
162
+ date: 140,
163
+ checkbox: 110,
164
+ select: 160
165
+ };
166
+ function HeaderLabel({
167
+ column,
168
+ onEdit,
169
+ label
170
+ }) {
171
+ const Icon = COLUMN_TYPE_ICONS[column.type];
172
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "group/dbh flex min-w-0 items-center gap-1.5", children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-3.5 shrink-0 text-muted-foreground", "aria-hidden": true }),
174
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[12.5px] font-medium", children: column.name }),
175
+ onEdit && /* @__PURE__ */ jsxRuntime.jsx(
176
+ "button",
177
+ {
178
+ type: "button",
179
+ "aria-label": label,
180
+ title: label,
181
+ "data-testid": `database-column-settings-${column.id}`,
182
+ onClick: (e) => {
183
+ e.stopPropagation();
184
+ onEdit();
185
+ },
186
+ onMouseDown: (e) => e.stopPropagation(),
187
+ className: "ml-auto rounded p-0.5 text-muted-foreground opacity-0 hover:bg-muted hover:text-foreground focus-visible:opacity-100 group-hover/dbh:opacity-100",
188
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "size-3.5" })
189
+ }
190
+ )
191
+ ] });
192
+ }
193
+ function columnWidth(column) {
194
+ return Math.min(320, Math.max(WIDTHS[column.type], Math.round(column.name.length * 8) + 76));
195
+ }
196
+ function editorFor(column) {
197
+ switch (column.type) {
198
+ case "number":
199
+ return { cellEditor: "agNumberCellEditor", cellEditorParams: { showStepperButtons: false } };
200
+ case "date":
201
+ return { cellEditor: "agDateStringCellEditor" };
202
+ case "checkbox":
203
+ return { cellEditor: "agCheckboxCellEditor" };
204
+ case "select":
205
+ return {
206
+ cellEditor: SelectCellEditor,
207
+ cellEditorPopup: true,
208
+ cellEditorParams: { options: column.options ?? [] }
209
+ };
210
+ default:
211
+ return { cellEditor: "agTextCellEditor" };
212
+ }
213
+ }
214
+ function rendererFor(column, locale) {
215
+ switch (column.type) {
216
+ case "checkbox":
217
+ return {
218
+ cellRenderer: "agCheckboxCellRenderer",
219
+ cellClass: "flex items-center justify-center"
220
+ };
221
+ case "select":
222
+ return {
223
+ cellRenderer: (p) => typeof p.value === "string" && p.value ? /* @__PURE__ */ jsxRuntime.jsx(SelectChip, { value: p.value }) : null
224
+ };
225
+ case "number":
226
+ return {
227
+ cellClass: "cell-numeric",
228
+ valueFormatter: (p) => formatCell(p.value, "number", locale)
229
+ };
230
+ case "date":
231
+ return {
232
+ valueFormatter: (p) => formatCell(p.value, "date", locale)
233
+ };
234
+ default:
235
+ return {};
236
+ }
237
+ }
238
+ function createDatabaseColumns(columns, options = {}) {
239
+ const { readOnly, onEditColumn, onAddColumn, locale } = options;
240
+ const l = { ...DEFAULT_DATABASE_COLUMN_LABELS, ...options.labels };
241
+ const defs = columns.map((column) => ({
242
+ colId: column.id,
243
+ headerName: column.name,
244
+ width: columnWidth(column),
245
+ minWidth: 90,
246
+ resizable: true,
247
+ sortable: true,
248
+ editable: !readOnly,
249
+ // The grid's own type inference must not second-guess the database's column type.
250
+ cellDataType: false,
251
+ headerComponentParams: {
252
+ innerHeaderComponent: () => /* @__PURE__ */ jsxRuntime.jsx(
253
+ HeaderLabel,
254
+ {
255
+ column,
256
+ label: l.columnSettings(column.name),
257
+ onEdit: !readOnly && onEditColumn ? () => onEditColumn(column) : void 0
258
+ }
259
+ )
260
+ },
261
+ // An empty checkbox reads as unticked (not indeterminate).
262
+ valueGetter: (p) => column.type === "checkbox" ? p.data?.values[column.id] === true : p.data?.values[column.id] ?? null,
263
+ // Edits land in the row's values as the API would store them; the host saves them in
264
+ // onCellValueChanged and puts the server's normalised row back.
265
+ valueSetter: (p) => {
266
+ if (!p.data) return false;
267
+ const next = toApiValue(p.newValue, column.type);
268
+ if (next === (p.oldValue ?? null)) return false;
269
+ p.data.values = { ...p.data.values, [column.id]: next };
270
+ return true;
271
+ },
272
+ comparator: (a, b) => compareCells(a, b, column.type),
273
+ getQuickFilterText: (p) => formatCell(p.value, column.type, locale),
274
+ ...editorFor(column),
275
+ ...rendererFor(column, locale)
276
+ }));
277
+ if (!readOnly && onAddColumn) {
278
+ defs.push({
279
+ colId: ADD_COLUMN_COL_ID,
280
+ width: 52,
281
+ minWidth: 52,
282
+ maxWidth: 52,
283
+ resizable: false,
284
+ sortable: false,
285
+ editable: false,
286
+ suppressMovable: true,
287
+ // Pinned: on a wide table the add button stays in view instead of scrolling off the right.
288
+ pinned: "right",
289
+ lockPinned: true,
290
+ headerComponent: () => /* @__PURE__ */ jsxRuntime.jsx(
291
+ "button",
292
+ {
293
+ type: "button",
294
+ "aria-label": l.addColumn,
295
+ title: l.addColumn,
296
+ "data-testid": "database-add-column",
297
+ onClick: onAddColumn,
298
+ className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
299
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "size-4" })
300
+ }
301
+ ),
302
+ valueGetter: () => null
303
+ });
304
+ }
305
+ return defs;
306
+ }
307
+ var escapeHtml = (text) => text.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
308
+ var DEFAULT_DATABASE_TABLE_LABELS = {
309
+ newRow: "New row",
310
+ emptyTitle: "No rows yet",
311
+ emptyMessage: "Add a row, or import a spreadsheet.",
312
+ noColumnsTitle: "No columns yet",
313
+ noColumnsMessage: "Add a column to start.",
314
+ noMatches: "No rows match your search."
315
+ };
316
+ function DatabaseTable({
317
+ columns,
318
+ rows,
319
+ onCellChange,
320
+ onAddRow,
321
+ onAddColumn,
322
+ onEditColumn,
323
+ onSelectionChange,
324
+ selected,
325
+ readOnly = false,
326
+ isFiltered = false,
327
+ isLoading,
328
+ locale,
329
+ gridId,
330
+ height = "100%",
331
+ labels,
332
+ columnLabels,
333
+ gridProps,
334
+ testId
335
+ }) {
336
+ const l = { ...DEFAULT_DATABASE_TABLE_LABELS, ...labels };
337
+ const columnDefs = react.useMemo(
338
+ () => createDatabaseColumns(columns, {
339
+ readOnly,
340
+ onEditColumn,
341
+ onAddColumn,
342
+ locale,
343
+ labels: columnLabels
344
+ }),
345
+ [columns, readOnly, onEditColumn, onAddColumn, locale, columnLabels]
346
+ );
347
+ const noColumns = columns.length === 0;
348
+ const emptyState = {
349
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Table2, { className: "size-8 text-muted-foreground" }),
350
+ title: l.noColumnsTitle,
351
+ message: readOnly ? void 0 : l.noColumnsMessage,
352
+ action: !readOnly && onAddColumn ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: onAddColumn, "data-testid": "database-empty-add-column", className: "gap-1.5", children: [
353
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "size-4" }),
354
+ columnLabels?.addColumn ?? DEFAULT_DATABASE_COLUMN_LABELS.addColumn
355
+ ] }) : void 0
356
+ };
357
+ const noRowsText = isFiltered ? l.noMatches : readOnly ? l.emptyTitle : `${l.emptyTitle}. ${l.emptyMessage}`;
358
+ const api = react.useRef(null);
359
+ const knownColumns = react.useRef(null);
360
+ react.useEffect(() => {
361
+ const ids = new Set(columns.map((c) => c.id));
362
+ const added = knownColumns.current ? columns.filter((c) => !knownColumns.current.has(c.id)) : [];
363
+ knownColumns.current = ids;
364
+ const last = added[added.length - 1];
365
+ if (last && api.current) {
366
+ const grid = api.current;
367
+ setTimeout(() => grid.ensureColumnVisible(last.id, "end"), 0);
368
+ }
369
+ }, [columns]);
370
+ const handleCellValueChanged = (event) => {
371
+ const columnId = event.column.getColId();
372
+ if (!event.data || !onCellChange) return;
373
+ onCellChange(event.data, columnId, event.data.values[columnId] ?? null);
374
+ };
375
+ return /* @__PURE__ */ jsxRuntime.jsxs(
376
+ "div",
377
+ {
378
+ className: "flex h-full min-h-0 flex-col",
379
+ "data-testid": chunkMHVNT4J3_cjs.testIdOr(testId, { testId: "database-table" }),
380
+ children: [
381
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
382
+ chunkIXALYQT7_cjs.GridView,
383
+ {
384
+ testId: "database-grid",
385
+ items: rows,
386
+ columnDefs,
387
+ getItemId: (row) => row.id,
388
+ getRowTestId: (row) => `database-row-${row.id}`,
389
+ getCellTestId: (colId, row) => `database-cell-${row.id}-${colId}`,
390
+ isLoading,
391
+ emptyState,
392
+ showEmpty: noColumns,
393
+ gridId,
394
+ height,
395
+ rowHeight: 38,
396
+ selection: onSelectionChange ? { mode: "multiple", selected: selected ?? [], onSelectionChange, controlled: true } : void 0,
397
+ onCellValueChanged: handleCellValueChanged,
398
+ ...gridProps,
399
+ onGridReady: (params) => {
400
+ api.current = params.api;
401
+ gridProps?.onGridReady?.(params);
402
+ },
403
+ gridOptions: {
404
+ overlayNoRowsTemplate: `<span class="text-sm text-muted-foreground">${escapeHtml(noRowsText)}</span>`,
405
+ singleClickEdit: false,
406
+ stopEditingWhenCellsLoseFocus: true,
407
+ enterNavigatesVerticallyAfterEdit: true,
408
+ undoRedoCellEditing: false,
409
+ ...gridProps?.gridOptions
410
+ }
411
+ }
412
+ ) }),
413
+ !readOnly && onAddRow && columns.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border px-2 py-1", children: /* @__PURE__ */ jsxRuntime.jsxs(
414
+ ui.Button,
415
+ {
416
+ variant: "ghost",
417
+ size: "sm",
418
+ onClick: onAddRow,
419
+ "data-testid": "database-add-row",
420
+ className: "gap-1.5 text-muted-foreground",
421
+ children: [
422
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "size-4" }),
423
+ l.newRow
424
+ ]
425
+ }
426
+ ) })
427
+ ]
428
+ }
429
+ );
430
+ }
431
+ var DEFAULT_DATABASE_TOOLBAR_LABELS = {
432
+ back: "All databases",
433
+ rename: "Rename database",
434
+ rows: (n) => `${n.toLocaleString()} row${n === 1 ? "" : "s"}`,
435
+ showing: (shown, total) => `Showing ${shown.toLocaleString()} of ${total.toLocaleString()}`,
436
+ search: "Search",
437
+ deleteSelected: (n) => `Delete ${n} row${n === 1 ? "" : "s"}`,
438
+ more: "More actions",
439
+ importRows: "Import rows from a spreadsheet\u2026",
440
+ exportXlsx: "Export to Excel (.xlsx)",
441
+ exportCsv: "Export to CSV",
442
+ deleteDatabase: "Delete database",
443
+ readOnly: "Read-only here: this database belongs to another workspace."
444
+ };
445
+ var SPREADSHEET_ACCEPT = ".csv,.xlsx,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
446
+ function DatabaseToolbar({
447
+ name,
448
+ description,
449
+ total,
450
+ shown,
451
+ search,
452
+ onSearchChange,
453
+ onBack,
454
+ onRename,
455
+ selectedCount = 0,
456
+ onDeleteSelected,
457
+ onImportRows,
458
+ onExport,
459
+ onDeleteDatabase,
460
+ readOnly = false,
461
+ labels
462
+ }) {
463
+ const l = { ...DEFAULT_DATABASE_TOOLBAR_LABELS, ...labels };
464
+ const [title, setTitle] = react.useState(name);
465
+ const fileInput = react.useRef(null);
466
+ react.useEffect(() => setTitle(name), [name]);
467
+ const commitTitle = () => {
468
+ const next = title.trim();
469
+ if (next && next !== name) onRename?.(next);
470
+ else setTitle(name);
471
+ };
472
+ const onTitleKey = (e) => {
473
+ if (e.key === "Enter") e.currentTarget.blur();
474
+ if (e.key === "Escape") {
475
+ setTitle(name);
476
+ e.currentTarget.blur();
477
+ }
478
+ };
479
+ const onFile = (e) => {
480
+ const file = e.target.files?.[0];
481
+ if (file) onImportRows?.(file);
482
+ e.target.value = "";
483
+ };
484
+ const canRename = !readOnly && Boolean(onRename);
485
+ const hasMenu = Boolean(onExport || !readOnly && (onImportRows || onDeleteDatabase));
486
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-2 pb-2 pt-1", "data-testid": "database-toolbar", children: [
487
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
488
+ onBack && /* @__PURE__ */ jsxRuntime.jsx(
489
+ ui.Button,
490
+ {
491
+ variant: "ghost",
492
+ size: "icon",
493
+ onClick: onBack,
494
+ "aria-label": l.back,
495
+ title: l.back,
496
+ "data-testid": "database-back",
497
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "size-4" })
498
+ }
499
+ ),
500
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileSpreadsheet, { className: "size-5 shrink-0 text-muted-foreground", "aria-hidden": true }),
501
+ canRename ? /* @__PURE__ */ jsxRuntime.jsx(
502
+ "input",
503
+ {
504
+ value: title,
505
+ "aria-label": l.rename,
506
+ maxLength: 200,
507
+ onChange: (e) => setTitle(e.target.value),
508
+ onBlur: commitTitle,
509
+ onKeyDown: onTitleKey,
510
+ "data-testid": "database-title",
511
+ className: "min-w-0 flex-1 truncate rounded-md bg-transparent px-1 text-xl font-semibold outline-none hover:bg-muted/60 focus:bg-muted/60"
512
+ }
513
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
514
+ "h2",
515
+ {
516
+ className: "min-w-0 flex-1 truncate px-1 text-xl font-semibold",
517
+ "data-testid": "database-title",
518
+ children: name
519
+ }
520
+ ),
521
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-sm text-muted-foreground", "data-testid": "database-row-count", children: shown !== void 0 && shown < total ? l.showing(shown, total) : l.rows(total) })
522
+ ] }),
523
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-1 text-sm text-muted-foreground", children: description }),
524
+ readOnly && /* @__PURE__ */ jsxRuntime.jsxs(
525
+ "p",
526
+ {
527
+ className: "flex items-center gap-1.5 rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground",
528
+ "data-testid": "database-read-only",
529
+ children: [
530
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "size-3.5" }),
531
+ l.readOnly
532
+ ]
533
+ }
534
+ ),
535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
536
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative max-w-sm flex-1", children: [
537
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
538
+ /* @__PURE__ */ jsxRuntime.jsx(
539
+ ui.Input,
540
+ {
541
+ value: search,
542
+ onChange: (e) => onSearchChange(e.target.value),
543
+ placeholder: l.search,
544
+ "aria-label": l.search,
545
+ className: "h-8 pl-8",
546
+ "data-testid": "database-search"
547
+ }
548
+ )
549
+ ] }),
550
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1" }),
551
+ !readOnly && selectedCount > 0 && onDeleteSelected && /* @__PURE__ */ jsxRuntime.jsxs(
552
+ ui.Button,
553
+ {
554
+ variant: "outline",
555
+ size: "sm",
556
+ onClick: onDeleteSelected,
557
+ className: "gap-1.5 text-red-text",
558
+ "data-testid": "database-delete-rows",
559
+ children: [
560
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "size-4" }),
561
+ l.deleteSelected(selectedCount)
562
+ ]
563
+ }
564
+ ),
565
+ hasMenu && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
566
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
567
+ ui.Button,
568
+ {
569
+ variant: "outline",
570
+ size: "icon",
571
+ "aria-label": l.more,
572
+ title: l.more,
573
+ "data-testid": "database-menu",
574
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "size-4" })
575
+ }
576
+ ) }),
577
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuContent, { align: "end", className: "w-64", children: [
578
+ !readOnly && onImportRows && /* @__PURE__ */ jsxRuntime.jsxs(
579
+ ui.DropdownMenuItem,
580
+ {
581
+ onSelect: () => fileInput.current?.click(),
582
+ "data-testid": "database-import-rows",
583
+ children: [
584
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "size-4" }),
585
+ l.importRows
586
+ ]
587
+ }
588
+ ),
589
+ onExport && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
590
+ /* @__PURE__ */ jsxRuntime.jsxs(
591
+ ui.DropdownMenuItem,
592
+ {
593
+ onSelect: () => onExport("xlsx"),
594
+ "data-testid": "database-export-xlsx",
595
+ children: [
596
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "size-4" }),
597
+ l.exportXlsx
598
+ ]
599
+ }
600
+ ),
601
+ /* @__PURE__ */ jsxRuntime.jsxs(
602
+ ui.DropdownMenuItem,
603
+ {
604
+ onSelect: () => onExport("csv"),
605
+ "data-testid": "database-export-csv",
606
+ children: [
607
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "size-4" }),
608
+ l.exportCsv
609
+ ]
610
+ }
611
+ )
612
+ ] }),
613
+ !readOnly && onDeleteDatabase && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
614
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuSeparator, {}),
615
+ /* @__PURE__ */ jsxRuntime.jsxs(
616
+ ui.DropdownMenuItem,
617
+ {
618
+ onSelect: onDeleteDatabase,
619
+ className: "text-red-text",
620
+ "data-testid": "database-delete",
621
+ children: [
622
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "size-4" }),
623
+ l.deleteDatabase
624
+ ]
625
+ }
626
+ )
627
+ ] })
628
+ ] })
629
+ ] }),
630
+ /* @__PURE__ */ jsxRuntime.jsx(
631
+ "input",
632
+ {
633
+ ref: fileInput,
634
+ type: "file",
635
+ accept: SPREADSHEET_ACCEPT,
636
+ className: "hidden",
637
+ onChange: onFile,
638
+ "data-testid": "database-import-rows-input"
639
+ }
640
+ )
641
+ ] })
642
+ ] });
643
+ }
644
+ var DEFAULT_DATABASE_COLUMN_DIALOG_LABELS = {
645
+ addTitle: "Add a column",
646
+ editTitle: "Column settings",
647
+ addDescription: "Pick what the column holds; every cell in it is checked against its type.",
648
+ editDescription: "Rename the column or change its options.",
649
+ name: "Name",
650
+ namePlaceholder: "e.g. Client, Matter number, Fee",
651
+ type: "Type",
652
+ typeFixed: "A column\u2019s type can\u2019t be changed once it has been added.",
653
+ options: "Options",
654
+ optionPlaceholder: "Add an option and press Enter",
655
+ addOption: "Add",
656
+ removeOption: (option) => `Remove ${option}`,
657
+ needsOptions: "A select column needs at least one option.",
658
+ cancel: "Cancel",
659
+ add: "Add column",
660
+ save: "Save",
661
+ saving: "Saving\u2026",
662
+ delete: "Delete column",
663
+ confirmDelete: "Delete this column and every value in it?"
664
+ };
665
+ function DatabaseColumnDialog({
666
+ open,
667
+ onOpenChange,
668
+ column,
669
+ onSave,
670
+ onDelete,
671
+ isSaving = false,
672
+ error,
673
+ labels,
674
+ typeLabels
675
+ }) {
676
+ const l = { ...DEFAULT_DATABASE_COLUMN_DIALOG_LABELS, ...labels };
677
+ const tl = { ...DEFAULT_DATABASE_COLUMN_TYPE_LABELS, ...typeLabels };
678
+ const editing = Boolean(column);
679
+ const [name, setName] = react.useState("");
680
+ const [type, setType] = react.useState("text");
681
+ const [options, setOptions] = react.useState([]);
682
+ const [optionDraft, setOptionDraft] = react.useState("");
683
+ const [confirmingDelete, setConfirmingDelete] = react.useState(false);
684
+ react.useEffect(() => {
685
+ if (!open) return;
686
+ setName(column?.name ?? "");
687
+ setType(column?.type ?? "text");
688
+ setOptions(column?.options ?? []);
689
+ setOptionDraft("");
690
+ setConfirmingDelete(false);
691
+ }, [open, column]);
692
+ const addOption = () => {
693
+ const option = optionDraft.trim();
694
+ if (option && !options.some((o) => o.toLowerCase() === option.toLowerCase())) {
695
+ setOptions([...options, option]);
696
+ }
697
+ setOptionDraft("");
698
+ };
699
+ const pendingOptions = optionDraft.trim() ? [...options, optionDraft.trim()] : options;
700
+ const missingOptions = type === "select" && pendingOptions.length === 0;
701
+ const canSave = name.trim().length > 0 && !missingOptions && !isSaving;
702
+ const submit = (e) => {
703
+ e.preventDefault();
704
+ if (!canSave) return;
705
+ onSave({
706
+ name: name.trim(),
707
+ type,
708
+ options: type === "select" ? Array.from(new Set(pendingOptions)) : null
709
+ });
710
+ };
711
+ const onOptionKey = (e) => {
712
+ if (e.key === "Enter") {
713
+ e.preventDefault();
714
+ addOption();
715
+ }
716
+ };
717
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DialogContent, { className: "sm:max-w-md", "data-testid": "database-column-dialog", children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "flex flex-col gap-4", children: [
718
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogHeader, { children: [
719
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DialogTitle, { children: editing ? l.editTitle : l.addTitle }),
720
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DialogDescription, { children: editing ? l.editDescription : l.addDescription })
721
+ ] }),
722
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
723
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "database-column-name", children: l.name }),
724
+ /* @__PURE__ */ jsxRuntime.jsx(
725
+ ui.Input,
726
+ {
727
+ id: "database-column-name",
728
+ autoFocus: true,
729
+ value: name,
730
+ maxLength: 200,
731
+ placeholder: l.namePlaceholder,
732
+ onChange: (e) => setName(e.target.value),
733
+ "data-testid": "database-column-name"
734
+ }
735
+ )
736
+ ] }),
737
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
738
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: l.type }),
739
+ /* @__PURE__ */ jsxRuntime.jsxs(
740
+ ui.Select,
741
+ {
742
+ value: type,
743
+ onValueChange: (v) => setType(v),
744
+ disabled: editing,
745
+ children: [
746
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { "data-testid": "database-column-type", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
747
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: DATABASE_COLUMN_TYPES.map((t) => {
748
+ const Icon = COLUMN_TYPE_ICONS[t];
749
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: t, "data-testid": `database-column-type-${t}`, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
750
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4 text-muted-foreground" }),
751
+ tl[t]
752
+ ] }) }, t);
753
+ }) })
754
+ ]
755
+ }
756
+ ),
757
+ editing && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: l.typeFixed })
758
+ ] }),
759
+ type === "select" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
760
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "database-column-option", children: l.options }),
761
+ options.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", "data-testid": "database-column-options", children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-0.5", children: [
762
+ /* @__PURE__ */ jsxRuntime.jsx(SelectChip, { value: option }),
763
+ /* @__PURE__ */ jsxRuntime.jsx(
764
+ "button",
765
+ {
766
+ type: "button",
767
+ "aria-label": l.removeOption(option),
768
+ onClick: () => setOptions(options.filter((o) => o !== option)),
769
+ className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
770
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-3" })
771
+ }
772
+ )
773
+ ] }, option)) }),
774
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
775
+ /* @__PURE__ */ jsxRuntime.jsx(
776
+ ui.Input,
777
+ {
778
+ id: "database-column-option",
779
+ value: optionDraft,
780
+ maxLength: 200,
781
+ placeholder: l.optionPlaceholder,
782
+ onChange: (e) => setOptionDraft(e.target.value),
783
+ onKeyDown: onOptionKey,
784
+ "data-testid": "database-column-option-input"
785
+ }
786
+ ),
787
+ /* @__PURE__ */ jsxRuntime.jsx(
788
+ ui.Button,
789
+ {
790
+ type: "button",
791
+ variant: "outline",
792
+ onClick: addOption,
793
+ disabled: !optionDraft.trim(),
794
+ children: l.addOption
795
+ }
796
+ )
797
+ ] }),
798
+ missingOptions && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: l.needsOptions })
799
+ ] }),
800
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-sm text-red-text", "data-testid": "database-column-error", children: error }),
801
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogFooter, { className: "flex-row items-center gap-2 sm:justify-between", children: [
802
+ editing && onDelete ? confirmingDelete ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 text-sm", children: [
803
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: l.confirmDelete }),
804
+ /* @__PURE__ */ jsxRuntime.jsx(
805
+ ui.Button,
806
+ {
807
+ type: "button",
808
+ variant: "destructive",
809
+ size: "sm",
810
+ onClick: onDelete,
811
+ "data-testid": "database-column-delete-confirm",
812
+ children: l.delete
813
+ }
814
+ )
815
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
816
+ ui.Button,
817
+ {
818
+ type: "button",
819
+ variant: "ghost",
820
+ size: "sm",
821
+ onClick: () => setConfirmingDelete(true),
822
+ className: "gap-1.5 text-red-text",
823
+ "data-testid": "database-column-delete",
824
+ children: [
825
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "size-4" }),
826
+ l.delete
827
+ ]
828
+ }
829
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
830
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex gap-2", children: [
831
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: l.cancel }),
832
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: !canSave, "data-testid": "database-column-save", children: isSaving ? l.saving : editing ? l.save : l.add })
833
+ ] })
834
+ ] })
835
+ ] }) }) });
836
+ }
837
+ var DEFAULT_DATABASE_LIST_LABELS = {
838
+ title: "Databases",
839
+ subtitle: "Tables your team keeps and searches \u2014 a client database, billing records, precedents.",
840
+ newDatabase: "New database",
841
+ importSpreadsheet: "Import spreadsheet",
842
+ importing: "Importing\u2026",
843
+ ownGroup: "In this workspace",
844
+ sharedGroup: "Shared with everyone",
845
+ readOnly: "Read-only",
846
+ rows: (n) => `${n.toLocaleString()} row${n === 1 ? "" : "s"}`,
847
+ columns: (n) => `${n} column${n === 1 ? "" : "s"}`,
848
+ emptyTitle: "No databases yet",
849
+ emptyMessage: "Start from a blank table, or import a spreadsheet (.xlsx or .csv) \u2014 its columns and their types come with it.",
850
+ loading: "Loading databases\u2026"
851
+ };
852
+ var SPREADSHEET_ACCEPT2 = ".csv,.xlsx,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
853
+ function DatabaseCard({
854
+ database,
855
+ onOpen,
856
+ l
857
+ }) {
858
+ return /* @__PURE__ */ jsxRuntime.jsxs(
859
+ "button",
860
+ {
861
+ type: "button",
862
+ onClick: onOpen,
863
+ "data-testid": `database-card-${database.id}`,
864
+ className: ui.cn(
865
+ "flex min-h-32 flex-col gap-2 rounded-lg border border-border bg-background p-4 text-left transition-colors",
866
+ "hover:border-primary/40 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
867
+ ),
868
+ children: [
869
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex w-full items-start gap-2", children: [
870
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileSpreadsheet, { className: "mt-0.5 size-5 shrink-0 text-muted-foreground", "aria-hidden": true }),
871
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: database.name }),
872
+ !database.editable && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground", children: [
873
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "size-3" }),
874
+ l.readOnly
875
+ ] })
876
+ ] }),
877
+ database.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "line-clamp-2 text-sm text-muted-foreground", children: database.description }),
878
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mt-auto flex w-full items-center gap-3 text-xs text-muted-foreground", children: [
879
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: l.rows(database.row_count) }),
880
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: l.columns(database.columns.length) }),
881
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto flex items-center gap-1", "aria-hidden": true, children: database.columns.slice(0, 6).map((c) => {
882
+ const Icon = COLUMN_TYPE_ICONS[c.type];
883
+ return /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-3.5" }, c.id);
884
+ }) })
885
+ ] })
886
+ ]
887
+ }
888
+ );
889
+ }
890
+ function DatabaseList({
891
+ databases,
892
+ onOpen,
893
+ onNew,
894
+ onImport,
895
+ isImporting = false,
896
+ isLoading = false,
897
+ labels
898
+ }) {
899
+ const l = { ...DEFAULT_DATABASE_LIST_LABELS, ...labels };
900
+ const fileInput = react.useRef(null);
901
+ const own = databases.filter((d) => d.editable);
902
+ const shared = databases.filter((d) => !d.editable);
903
+ const onFile = (e) => {
904
+ const file = e.target.files?.[0];
905
+ if (file) onImport?.(file);
906
+ e.target.value = "";
907
+ };
908
+ const actions = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex gap-2", children: [
909
+ onImport && /* @__PURE__ */ jsxRuntime.jsxs(
910
+ ui.Button,
911
+ {
912
+ variant: "outline",
913
+ onClick: () => fileInput.current?.click(),
914
+ disabled: isImporting,
915
+ className: "gap-1.5",
916
+ "data-testid": "database-import",
917
+ children: [
918
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "size-4" }),
919
+ isImporting ? l.importing : l.importSpreadsheet
920
+ ]
921
+ }
922
+ ),
923
+ onNew && /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: onNew, className: "gap-1.5", "data-testid": "database-new", children: [
924
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "size-4" }),
925
+ l.newDatabase
926
+ ] })
927
+ ] });
928
+ const grid = (items) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3", children: items.map((d) => /* @__PURE__ */ jsxRuntime.jsx(DatabaseCard, { database: d, onOpen: () => onOpen(d), l }, d.id)) });
929
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-6 p-2 sm:p-4", "data-testid": "database-list", children: [
930
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
931
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
932
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold", children: l.title }),
933
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: l.subtitle })
934
+ ] }),
935
+ actions
936
+ ] }),
937
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: l.loading }) : databases.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
938
+ "div",
939
+ {
940
+ className: "flex flex-col items-center gap-2 rounded-lg border border-dashed border-border px-6 py-12 text-center",
941
+ "data-testid": "database-list-empty",
942
+ children: [
943
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileSpreadsheet, { className: "size-8 text-muted-foreground" }),
944
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium", children: l.emptyTitle }),
945
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: l.emptyMessage })
946
+ ]
947
+ }
948
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
949
+ own.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex flex-col gap-2", children: [
950
+ shared.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-medium text-muted-foreground", children: l.ownGroup }),
951
+ grid(own)
952
+ ] }),
953
+ shared.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex flex-col gap-2", "data-testid": "database-list-shared", children: [
954
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-medium text-muted-foreground", children: l.sharedGroup }),
955
+ grid(shared)
956
+ ] })
957
+ ] }),
958
+ /* @__PURE__ */ jsxRuntime.jsx(
959
+ "input",
960
+ {
961
+ ref: fileInput,
962
+ type: "file",
963
+ accept: SPREADSHEET_ACCEPT2,
964
+ className: "hidden",
965
+ onChange: onFile,
966
+ "data-testid": "database-import-input"
967
+ }
968
+ )
969
+ ] });
970
+ }
971
+ var DEFAULT_NEW_DATABASE_DIALOG_LABELS = {
972
+ title: "New database",
973
+ description: "A table your workspace keeps: add columns of the kinds you need, then rows.",
974
+ name: "Name",
975
+ namePlaceholder: "e.g. Client database",
976
+ about: "Description",
977
+ aboutPlaceholder: "Optional: what it holds and who keeps it",
978
+ cancel: "Cancel",
979
+ create: "Create",
980
+ creating: "Creating\u2026"
981
+ };
982
+ function NewDatabaseDialog({
983
+ open,
984
+ onOpenChange,
985
+ onCreate,
986
+ isCreating = false,
987
+ error,
988
+ labels
989
+ }) {
990
+ const l = { ...DEFAULT_NEW_DATABASE_DIALOG_LABELS, ...labels };
991
+ const [name, setName] = react.useState("");
992
+ const [about, setAbout] = react.useState("");
993
+ react.useEffect(() => {
994
+ if (open) {
995
+ setName("");
996
+ setAbout("");
997
+ }
998
+ }, [open]);
999
+ const submit = (e) => {
1000
+ e.preventDefault();
1001
+ if (!name.trim() || isCreating) return;
1002
+ onCreate({ name: name.trim(), description: about.trim() || null });
1003
+ };
1004
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DialogContent, { className: "sm:max-w-md", "data-testid": "new-database-dialog", children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "flex flex-col gap-4", children: [
1005
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogHeader, { children: [
1006
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DialogTitle, { children: l.title }),
1007
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DialogDescription, { children: l.description })
1008
+ ] }),
1009
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
1010
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "new-database-name", children: l.name }),
1011
+ /* @__PURE__ */ jsxRuntime.jsx(
1012
+ ui.Input,
1013
+ {
1014
+ id: "new-database-name",
1015
+ autoFocus: true,
1016
+ value: name,
1017
+ maxLength: 200,
1018
+ placeholder: l.namePlaceholder,
1019
+ onChange: (e) => setName(e.target.value),
1020
+ "data-testid": "new-database-name"
1021
+ }
1022
+ )
1023
+ ] }),
1024
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
1025
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "new-database-about", children: l.about }),
1026
+ /* @__PURE__ */ jsxRuntime.jsx(
1027
+ ui.Textarea,
1028
+ {
1029
+ id: "new-database-about",
1030
+ value: about,
1031
+ maxLength: 2e3,
1032
+ rows: 2,
1033
+ placeholder: l.aboutPlaceholder,
1034
+ onChange: (e) => setAbout(e.target.value),
1035
+ "data-testid": "new-database-description"
1036
+ }
1037
+ )
1038
+ ] }),
1039
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-sm text-red-text", children: error }),
1040
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogFooter, { children: [
1041
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: l.cancel }),
1042
+ /* @__PURE__ */ jsxRuntime.jsx(
1043
+ ui.Button,
1044
+ {
1045
+ type: "submit",
1046
+ disabled: !name.trim() || isCreating,
1047
+ "data-testid": "new-database-create",
1048
+ children: isCreating ? l.creating : l.create
1049
+ }
1050
+ )
1051
+ ] })
1052
+ ] }) }) });
1053
+ }
1054
+
1055
+ exports.ADD_COLUMN_COL_ID = ADD_COLUMN_COL_ID;
1056
+ exports.COLUMN_TYPE_ICONS = COLUMN_TYPE_ICONS;
1057
+ exports.DATABASE_COLUMN_TYPES = DATABASE_COLUMN_TYPES;
1058
+ exports.DEFAULT_DATABASE_COLUMN_DIALOG_LABELS = DEFAULT_DATABASE_COLUMN_DIALOG_LABELS;
1059
+ exports.DEFAULT_DATABASE_COLUMN_LABELS = DEFAULT_DATABASE_COLUMN_LABELS;
1060
+ exports.DEFAULT_DATABASE_COLUMN_TYPE_LABELS = DEFAULT_DATABASE_COLUMN_TYPE_LABELS;
1061
+ exports.DEFAULT_DATABASE_LIST_LABELS = DEFAULT_DATABASE_LIST_LABELS;
1062
+ exports.DEFAULT_DATABASE_TABLE_LABELS = DEFAULT_DATABASE_TABLE_LABELS;
1063
+ exports.DEFAULT_DATABASE_TOOLBAR_LABELS = DEFAULT_DATABASE_TOOLBAR_LABELS;
1064
+ exports.DEFAULT_NEW_DATABASE_DIALOG_LABELS = DEFAULT_NEW_DATABASE_DIALOG_LABELS;
1065
+ exports.DatabaseColumnDialog = DatabaseColumnDialog;
1066
+ exports.DatabaseList = DatabaseList;
1067
+ exports.DatabaseTable = DatabaseTable;
1068
+ exports.DatabaseToolbar = DatabaseToolbar;
1069
+ exports.NewDatabaseDialog = NewDatabaseDialog;
1070
+ exports.SelectCellEditor = SelectCellEditor;
1071
+ exports.SelectChip = SelectChip;
1072
+ exports.columnWidth = columnWidth;
1073
+ exports.compareCells = compareCells;
1074
+ exports.createDatabaseColumns = createDatabaseColumns;
1075
+ exports.formatCell = formatCell;
1076
+ exports.formatDateValue = formatDateValue;
1077
+ exports.formatNumberValue = formatNumberValue;
1078
+ exports.optionTone = optionTone;
1079
+ exports.toApiValue = toApiValue;
1080
+ //# sourceMappingURL=index.cjs.map
1081
+ //# sourceMappingURL=index.cjs.map