@comitor/ui 1.9.0 → 1.11.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 (105) hide show
  1. package/README.md +15 -4
  2. package/dist/data-grid/compose-refs.d.ts +19 -0
  3. package/dist/data-grid/compose-refs.js +41 -0
  4. package/dist/data-grid/compose-refs.js.map +1 -0
  5. package/dist/data-grid/data-grid-cell-variants.d.ts +15 -0
  6. package/dist/data-grid/data-grid-cell-variants.js +1734 -0
  7. package/dist/data-grid/data-grid-cell-variants.js.map +1 -0
  8. package/dist/data-grid/data-grid-cell-wrapper.d.ts +9 -0
  9. package/dist/data-grid/data-grid-cell-wrapper.js +179 -0
  10. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -0
  11. package/dist/data-grid/data-grid-cell.d.ts +8 -0
  12. package/dist/data-grid/data-grid-cell.js +101 -0
  13. package/dist/data-grid/data-grid-cell.js.map +1 -0
  14. package/dist/data-grid/data-grid-column-header.d.ts +12 -0
  15. package/dist/data-grid/data-grid-column-header.js +205 -0
  16. package/dist/data-grid/data-grid-column-header.js.map +1 -0
  17. package/dist/data-grid/data-grid-context-menu.d.ts +12 -0
  18. package/dist/data-grid/data-grid-context-menu.js +160 -0
  19. package/dist/data-grid/data-grid-context-menu.js.map +1 -0
  20. package/dist/data-grid/data-grid-filter-menu.d.ts +12 -0
  21. package/dist/data-grid/data-grid-filter-menu.js +714 -0
  22. package/dist/data-grid/data-grid-filter-menu.js.map +1 -0
  23. package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +20 -0
  24. package/dist/data-grid/data-grid-keyboard-shortcuts.js +227 -0
  25. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -0
  26. package/dist/data-grid/data-grid-paste-dialog.d.ts +11 -0
  27. package/dist/data-grid/data-grid-paste-dialog.js +103 -0
  28. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -0
  29. package/dist/data-grid/data-grid-presence.d.ts +14 -0
  30. package/dist/data-grid/data-grid-presence.js +16 -0
  31. package/dist/data-grid/data-grid-presence.js.map +1 -0
  32. package/dist/data-grid/data-grid-row-height-menu.d.ts +15 -0
  33. package/dist/data-grid/data-grid-row-height-menu.js +56 -0
  34. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -0
  35. package/dist/data-grid/data-grid-row.d.ts +28 -0
  36. package/dist/data-grid/data-grid-row.js +206 -0
  37. package/dist/data-grid/data-grid-row.js.map +1 -0
  38. package/dist/data-grid/data-grid-search.d.ts +10 -0
  39. package/dist/data-grid/data-grid-search.js +183 -0
  40. package/dist/data-grid/data-grid-search.js.map +1 -0
  41. package/dist/data-grid/data-grid-select-column.d.ts +12 -0
  42. package/dist/data-grid/data-grid-select-column.js +177 -0
  43. package/dist/data-grid/data-grid-select-column.js.map +1 -0
  44. package/dist/data-grid/data-grid-skeleton.d.ts +13 -0
  45. package/dist/data-grid/data-grid-skeleton.js +52 -0
  46. package/dist/data-grid/data-grid-skeleton.js.map +1 -0
  47. package/dist/data-grid/data-grid-sort-menu.d.ts +12 -0
  48. package/dist/data-grid/data-grid-sort-menu.js +271 -0
  49. package/dist/data-grid/data-grid-sort-menu.js.map +1 -0
  50. package/dist/data-grid/data-grid-view-menu.d.ts +13 -0
  51. package/dist/data-grid/data-grid-view-menu.js +66 -0
  52. package/dist/data-grid/data-grid-view-menu.js.map +1 -0
  53. package/dist/data-grid/data-grid.d.ts +32 -0
  54. package/dist/data-grid/data-grid.js +235 -0
  55. package/dist/data-grid/data-grid.js.map +1 -0
  56. package/dist/data-grid/filters.d.ts +31 -0
  57. package/dist/data-grid/filters.js +212 -0
  58. package/dist/data-grid/filters.js.map +1 -0
  59. package/dist/data-grid/index.d.ts +22 -0
  60. package/dist/data-grid/index.js +77 -0
  61. package/dist/data-grid/index.js.map +1 -0
  62. package/dist/data-grid/messages.d.ts +280 -0
  63. package/dist/data-grid/messages.js +279 -0
  64. package/dist/data-grid/messages.js.map +1 -0
  65. package/dist/data-grid/sortable.d.ts +50 -0
  66. package/dist/data-grid/sortable.js +361 -0
  67. package/dist/data-grid/sortable.js.map +1 -0
  68. package/dist/data-grid/toast.d.ts +10 -0
  69. package/dist/data-grid/toast.js +21 -0
  70. package/dist/data-grid/toast.js.map +1 -0
  71. package/dist/data-grid/types.d.ts +189 -0
  72. package/dist/data-grid/types.js +1 -0
  73. package/dist/data-grid/types.js.map +1 -0
  74. package/dist/data-grid/use-as-ref.d.ts +5 -0
  75. package/dist/data-grid/use-as-ref.js +13 -0
  76. package/dist/data-grid/use-as-ref.js.map +1 -0
  77. package/dist/data-grid/use-badge-overflow.d.ts +24 -0
  78. package/dist/data-grid/use-badge-overflow.js +123 -0
  79. package/dist/data-grid/use-badge-overflow.js.map +1 -0
  80. package/dist/data-grid/use-callback-ref.d.ts +10 -0
  81. package/dist/data-grid/use-callback-ref.js +12 -0
  82. package/dist/data-grid/use-callback-ref.js.map +1 -0
  83. package/dist/data-grid/use-data-grid-undo-redo.d.ts +33 -0
  84. package/dist/data-grid/use-data-grid-undo-redo.js +345 -0
  85. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -0
  86. package/dist/data-grid/use-data-grid.d.ts +92 -0
  87. package/dist/data-grid/use-data-grid.js +2692 -0
  88. package/dist/data-grid/use-data-grid.js.map +1 -0
  89. package/dist/data-grid/use-debounced-callback.d.ts +6 -0
  90. package/dist/data-grid/use-debounced-callback.js +19 -0
  91. package/dist/data-grid/use-debounced-callback.js.map +1 -0
  92. package/dist/data-grid/use-isomorphic-layout-effect.d.ts +5 -0
  93. package/dist/data-grid/use-isomorphic-layout-effect.js +6 -0
  94. package/dist/data-grid/use-isomorphic-layout-effect.js.map +1 -0
  95. package/dist/data-grid/use-lazy-ref.d.ts +5 -0
  96. package/dist/data-grid/use-lazy-ref.js +12 -0
  97. package/dist/data-grid/use-lazy-ref.js.map +1 -0
  98. package/dist/data-grid/utils.d.ts +73 -0
  99. package/dist/data-grid/utils.js +408 -0
  100. package/dist/data-grid/utils.js.map +1 -0
  101. package/dist/shell/app-launcher.js +16 -16
  102. package/dist/shell/app-launcher.js.map +1 -1
  103. package/dist/shell/types.d.ts +13 -0
  104. package/dist/shell/types.js.map +1 -1
  105. package/package.json +33 -15
@@ -0,0 +1,1734 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { vi } from "date-fns/locale";
4
+ import { Check, Upload, X } from "lucide-react";
5
+ import * as React from "react";
6
+ import { Badge, badgeVariants } from "../components/ui/badge.js";
7
+ import { Button } from "../components/ui/button.js";
8
+ import { Calendar } from "../components/ui/calendar.js";
9
+ import { Checkbox } from "../components/ui/checkbox.js";
10
+ import {
11
+ Command,
12
+ CommandEmpty,
13
+ CommandGroup,
14
+ CommandInput,
15
+ CommandItem,
16
+ CommandList,
17
+ CommandSeparator
18
+ } from "../components/ui/command.js";
19
+ import { Popover, PopoverAnchor, PopoverContent } from "../components/ui/popover.js";
20
+ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select.js";
21
+ import { Skeleton } from "../components/ui/skeleton.js";
22
+ import { Textarea } from "../components/ui/textarea.js";
23
+ import { cn } from "../lib/cn.js";
24
+ import { DataGridCellWrapper } from "./data-grid-cell-wrapper.js";
25
+ import { msg } from "./messages.js";
26
+ import { toastError, toastMessage } from "./toast.js";
27
+ import { useBadgeOverflow } from "./use-badge-overflow.js";
28
+ import { useDebouncedCallback } from "./use-debounced-callback.js";
29
+ import {
30
+ formatDateForDisplay,
31
+ formatDateToString,
32
+ formatFileSize,
33
+ generateFileId,
34
+ getCellKey,
35
+ getFileIcon,
36
+ getLineCount,
37
+ getUrlHref,
38
+ parseLocalDate
39
+ } from "./utils.js";
40
+ function ShortTextCell({
41
+ cell,
42
+ tableMeta,
43
+ rowIndex,
44
+ columnId,
45
+ rowHeight,
46
+ isEditing,
47
+ isFocused,
48
+ isSelected,
49
+ isSearchMatch,
50
+ isActiveSearchMatch,
51
+ readOnly
52
+ }) {
53
+ const initialValue = cell.getValue();
54
+ const [value, setValue] = React.useState(initialValue);
55
+ const cellRef = React.useRef(null);
56
+ const containerRef = React.useRef(null);
57
+ const prevIsEditingRef = React.useRef(false);
58
+ const prevInitialValueRef = React.useRef(initialValue);
59
+ if (initialValue !== prevInitialValueRef.current) {
60
+ prevInitialValueRef.current = initialValue;
61
+ setValue(initialValue);
62
+ if (cellRef.current && !isEditing) {
63
+ cellRef.current.textContent = initialValue;
64
+ }
65
+ }
66
+ const onBlur = React.useCallback(() => {
67
+ const currentValue = cellRef.current?.textContent ?? "";
68
+ if (!readOnly && currentValue !== initialValue) {
69
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: currentValue });
70
+ }
71
+ tableMeta?.onCellEditingStop?.();
72
+ }, [tableMeta, rowIndex, columnId, initialValue, readOnly]);
73
+ const onInput = React.useCallback((event) => {
74
+ const currentValue = event.currentTarget.textContent ?? "";
75
+ setValue(currentValue);
76
+ }, []);
77
+ const onWrapperKeyDown = React.useCallback(
78
+ (event) => {
79
+ if (isEditing) {
80
+ if (event.key === "Enter") {
81
+ event.preventDefault();
82
+ const currentValue = cellRef.current?.textContent ?? "";
83
+ if (currentValue !== initialValue) {
84
+ tableMeta?.onDataUpdate?.({
85
+ rowIndex,
86
+ columnId,
87
+ value: currentValue
88
+ });
89
+ }
90
+ tableMeta?.onCellEditingStop?.({ moveToNextRow: true });
91
+ } else if (event.key === "Tab") {
92
+ event.preventDefault();
93
+ const currentValue = cellRef.current?.textContent ?? "";
94
+ if (currentValue !== initialValue) {
95
+ tableMeta?.onDataUpdate?.({
96
+ rowIndex,
97
+ columnId,
98
+ value: currentValue
99
+ });
100
+ }
101
+ tableMeta?.onCellEditingStop?.({
102
+ direction: event.shiftKey ? "left" : "right"
103
+ });
104
+ } else if (event.key === "Escape") {
105
+ event.preventDefault();
106
+ setValue(initialValue);
107
+ cellRef.current?.blur();
108
+ }
109
+ } else if (isFocused && event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
110
+ setValue(event.key);
111
+ queueMicrotask(() => {
112
+ if (cellRef.current && cellRef.current.contentEditable === "true") {
113
+ cellRef.current.textContent = event.key;
114
+ const range = document.createRange();
115
+ const selection = window.getSelection();
116
+ range.selectNodeContents(cellRef.current);
117
+ range.collapse(false);
118
+ selection?.removeAllRanges();
119
+ selection?.addRange(range);
120
+ }
121
+ });
122
+ }
123
+ },
124
+ [isEditing, isFocused, initialValue, tableMeta, rowIndex, columnId]
125
+ );
126
+ React.useEffect(() => {
127
+ const wasEditing = prevIsEditingRef.current;
128
+ prevIsEditingRef.current = isEditing;
129
+ if (isEditing && !wasEditing && cellRef.current) {
130
+ cellRef.current.focus();
131
+ if (!cellRef.current.textContent && value) {
132
+ cellRef.current.textContent = value;
133
+ }
134
+ if (cellRef.current.textContent) {
135
+ const range = document.createRange();
136
+ const selection = window.getSelection();
137
+ range.selectNodeContents(cellRef.current);
138
+ range.collapse(false);
139
+ selection?.removeAllRanges();
140
+ selection?.addRange(range);
141
+ }
142
+ }
143
+ }, [isEditing, value]);
144
+ const displayValue = !isEditing ? value ?? "" : "";
145
+ return /* @__PURE__ */ jsx(
146
+ DataGridCellWrapper,
147
+ {
148
+ ref: containerRef,
149
+ cell,
150
+ tableMeta,
151
+ rowIndex,
152
+ columnId,
153
+ rowHeight,
154
+ isEditing,
155
+ isFocused,
156
+ isSelected,
157
+ isSearchMatch,
158
+ isActiveSearchMatch,
159
+ readOnly,
160
+ onKeyDown: onWrapperKeyDown,
161
+ children: /* @__PURE__ */ jsx(
162
+ "div",
163
+ {
164
+ role: "textbox",
165
+ "data-slot": "grid-cell-content",
166
+ contentEditable: isEditing,
167
+ tabIndex: -1,
168
+ ref: cellRef,
169
+ onBlur,
170
+ onInput,
171
+ suppressContentEditableWarning: true,
172
+ className: cn("size-full overflow-hidden outline-none", {
173
+ "whitespace-nowrap [&_*]:inline [&_*]:whitespace-nowrap [&_br]:hidden": isEditing
174
+ }),
175
+ children: displayValue
176
+ }
177
+ )
178
+ }
179
+ );
180
+ }
181
+ function LongTextCell({
182
+ cell,
183
+ tableMeta,
184
+ rowIndex,
185
+ columnId,
186
+ rowHeight,
187
+ isFocused,
188
+ isEditing,
189
+ isSelected,
190
+ isSearchMatch,
191
+ isActiveSearchMatch,
192
+ readOnly
193
+ }) {
194
+ const initialValue = cell.getValue();
195
+ const [value, setValue] = React.useState(initialValue ?? "");
196
+ const textareaRef = React.useRef(null);
197
+ const containerRef = React.useRef(null);
198
+ const pendingCharRef = React.useRef(null);
199
+ const sideOffset = -(containerRef.current?.clientHeight ?? 0);
200
+ const prevInitialValueRef = React.useRef(initialValue);
201
+ if (initialValue !== prevInitialValueRef.current) {
202
+ prevInitialValueRef.current = initialValue;
203
+ setValue(initialValue ?? "");
204
+ }
205
+ const debouncedSave = useDebouncedCallback((newValue) => {
206
+ if (!readOnly) {
207
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValue });
208
+ }
209
+ }, 300);
210
+ const onSave = React.useCallback(() => {
211
+ if (!readOnly && value !== initialValue) {
212
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });
213
+ }
214
+ tableMeta?.onCellEditingStop?.();
215
+ }, [tableMeta, value, initialValue, rowIndex, columnId, readOnly]);
216
+ const onCancel = React.useCallback(() => {
217
+ setValue(initialValue ?? "");
218
+ if (!readOnly) {
219
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: initialValue });
220
+ }
221
+ tableMeta?.onCellEditingStop?.();
222
+ }, [tableMeta, initialValue, rowIndex, columnId, readOnly]);
223
+ const onOpenChange = React.useCallback(
224
+ (open) => {
225
+ if (open && !readOnly) {
226
+ tableMeta?.onCellEditingStart?.(rowIndex, columnId);
227
+ } else {
228
+ if (!readOnly && value !== initialValue) {
229
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });
230
+ }
231
+ tableMeta?.onCellEditingStop?.();
232
+ }
233
+ },
234
+ [tableMeta, value, initialValue, rowIndex, columnId, readOnly]
235
+ );
236
+ const onOpenAutoFocus = React.useCallback((event) => {
237
+ event.preventDefault();
238
+ if (textareaRef.current) {
239
+ textareaRef.current.focus();
240
+ const length = textareaRef.current.value.length;
241
+ textareaRef.current.setSelectionRange(length, length);
242
+ if (pendingCharRef.current) {
243
+ const char = pendingCharRef.current;
244
+ pendingCharRef.current = null;
245
+ requestAnimationFrame(() => {
246
+ if (textareaRef.current && document.activeElement === textareaRef.current) {
247
+ document.execCommand("insertText", false, char);
248
+ textareaRef.current.scrollTop = textareaRef.current.scrollHeight;
249
+ }
250
+ });
251
+ } else {
252
+ textareaRef.current.scrollTop = textareaRef.current.scrollHeight;
253
+ }
254
+ }
255
+ }, []);
256
+ const onWrapperKeyDown = React.useCallback(
257
+ (event) => {
258
+ if (isFocused && !isEditing && !readOnly && event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
259
+ pendingCharRef.current = event.key;
260
+ }
261
+ },
262
+ [isFocused, isEditing, readOnly]
263
+ );
264
+ const onBlur = React.useCallback(() => {
265
+ if (!readOnly && value !== initialValue) {
266
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });
267
+ }
268
+ tableMeta?.onCellEditingStop?.();
269
+ }, [tableMeta, value, initialValue, rowIndex, columnId, readOnly]);
270
+ const onChange = React.useCallback(
271
+ (event) => {
272
+ const newValue = event.target.value;
273
+ setValue(newValue);
274
+ debouncedSave(newValue);
275
+ },
276
+ [debouncedSave]
277
+ );
278
+ const onKeyDown = React.useCallback(
279
+ (event) => {
280
+ if (event.key === "Escape") {
281
+ event.preventDefault();
282
+ onCancel();
283
+ } else if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
284
+ event.preventDefault();
285
+ onSave();
286
+ } else if (event.key === "Tab") {
287
+ event.preventDefault();
288
+ if (value !== initialValue) {
289
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });
290
+ }
291
+ tableMeta?.onCellEditingStop?.({
292
+ direction: event.shiftKey ? "left" : "right"
293
+ });
294
+ return;
295
+ }
296
+ event.stopPropagation();
297
+ },
298
+ [onSave, onCancel, value, initialValue, tableMeta, rowIndex, columnId]
299
+ );
300
+ return /* @__PURE__ */ jsxs(Popover, { open: isEditing, onOpenChange, children: [
301
+ /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx(
302
+ DataGridCellWrapper,
303
+ {
304
+ ref: containerRef,
305
+ cell,
306
+ tableMeta,
307
+ rowIndex,
308
+ columnId,
309
+ rowHeight,
310
+ isEditing,
311
+ isFocused,
312
+ isSelected,
313
+ isSearchMatch,
314
+ isActiveSearchMatch,
315
+ readOnly,
316
+ onKeyDown: onWrapperKeyDown,
317
+ children: /* @__PURE__ */ jsx("span", { "data-slot": "grid-cell-content", children: value })
318
+ }
319
+ ) }),
320
+ /* @__PURE__ */ jsx(
321
+ PopoverContent,
322
+ {
323
+ "data-grid-cell-editor": "",
324
+ align: "start",
325
+ side: "bottom",
326
+ sideOffset,
327
+ className: "w-[400px] rounded-none p-0",
328
+ onOpenAutoFocus,
329
+ children: /* @__PURE__ */ jsx(
330
+ Textarea,
331
+ {
332
+ placeholder: msg.cell.longTextPlaceholder,
333
+ "aria-label": msg.cell.longTextPlaceholder,
334
+ className: "max-h-[300px] min-h-[150px] resize-none overflow-y-auto rounded-none border-0 shadow-none focus-visible:ring-1 focus-visible:ring-ring/30",
335
+ ref: textareaRef,
336
+ value,
337
+ onBlur,
338
+ onChange,
339
+ onKeyDown
340
+ }
341
+ )
342
+ }
343
+ )
344
+ ] });
345
+ }
346
+ function NumberCell({
347
+ cell,
348
+ tableMeta,
349
+ rowIndex,
350
+ columnId,
351
+ rowHeight,
352
+ isFocused,
353
+ isEditing,
354
+ isSelected,
355
+ isSearchMatch,
356
+ isActiveSearchMatch,
357
+ readOnly
358
+ }) {
359
+ const initialValue = cell.getValue();
360
+ const [value, setValue] = React.useState(String(initialValue ?? ""));
361
+ const inputRef = React.useRef(null);
362
+ const containerRef = React.useRef(null);
363
+ const cellOpts = cell.column.columnDef.meta?.cell;
364
+ const numberCellOpts = cellOpts?.variant === "number" ? cellOpts : null;
365
+ const min = numberCellOpts?.min;
366
+ const max = numberCellOpts?.max;
367
+ const step = numberCellOpts?.step;
368
+ const prevIsEditingRef = React.useRef(false);
369
+ const prevInitialValueRef = React.useRef(initialValue);
370
+ if (initialValue !== prevInitialValueRef.current) {
371
+ prevInitialValueRef.current = initialValue;
372
+ setValue(String(initialValue ?? ""));
373
+ }
374
+ const onBlur = React.useCallback(() => {
375
+ const numValue = value === "" ? null : Number(value);
376
+ if (!readOnly && numValue !== initialValue) {
377
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: numValue });
378
+ }
379
+ tableMeta?.onCellEditingStop?.();
380
+ }, [tableMeta, rowIndex, columnId, initialValue, value, readOnly]);
381
+ const onChange = React.useCallback((event) => {
382
+ setValue(event.target.value);
383
+ }, []);
384
+ const onWrapperKeyDown = React.useCallback(
385
+ (event) => {
386
+ if (isEditing) {
387
+ if (event.key === "Enter") {
388
+ event.preventDefault();
389
+ const numValue = value === "" ? null : Number(value);
390
+ if (numValue !== initialValue) {
391
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: numValue });
392
+ }
393
+ tableMeta?.onCellEditingStop?.({ moveToNextRow: true });
394
+ } else if (event.key === "Tab") {
395
+ event.preventDefault();
396
+ const numValue = value === "" ? null : Number(value);
397
+ if (numValue !== initialValue) {
398
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: numValue });
399
+ }
400
+ tableMeta?.onCellEditingStop?.({
401
+ direction: event.shiftKey ? "left" : "right"
402
+ });
403
+ } else if (event.key === "Escape") {
404
+ event.preventDefault();
405
+ setValue(String(initialValue ?? ""));
406
+ inputRef.current?.blur();
407
+ }
408
+ } else if (isFocused) {
409
+ if (event.key === "Backspace") {
410
+ setValue("");
411
+ } else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
412
+ setValue(event.key);
413
+ }
414
+ }
415
+ },
416
+ [isEditing, isFocused, initialValue, tableMeta, rowIndex, columnId, value]
417
+ );
418
+ React.useEffect(() => {
419
+ const wasEditing = prevIsEditingRef.current;
420
+ prevIsEditingRef.current = isEditing;
421
+ if (isEditing && !wasEditing && inputRef.current) {
422
+ inputRef.current.focus();
423
+ }
424
+ }, [isEditing]);
425
+ return /* @__PURE__ */ jsx(
426
+ DataGridCellWrapper,
427
+ {
428
+ ref: containerRef,
429
+ cell,
430
+ tableMeta,
431
+ rowIndex,
432
+ columnId,
433
+ rowHeight,
434
+ isEditing,
435
+ isFocused,
436
+ isSelected,
437
+ isSearchMatch,
438
+ isActiveSearchMatch,
439
+ readOnly,
440
+ onKeyDown: onWrapperKeyDown,
441
+ children: isEditing ? /* @__PURE__ */ jsx(
442
+ "input",
443
+ {
444
+ type: "number",
445
+ ref: inputRef,
446
+ value,
447
+ min,
448
+ max,
449
+ step,
450
+ className: "w-full border-none bg-transparent p-0 outline-none focus-visible:outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
451
+ onBlur,
452
+ onChange
453
+ }
454
+ ) : /* @__PURE__ */ jsx("span", { "data-slot": "grid-cell-content", children: value })
455
+ }
456
+ );
457
+ }
458
+ function UrlCell({
459
+ cell,
460
+ tableMeta,
461
+ rowIndex,
462
+ columnId,
463
+ rowHeight,
464
+ isEditing,
465
+ isFocused,
466
+ isSelected,
467
+ isSearchMatch,
468
+ isActiveSearchMatch,
469
+ readOnly
470
+ }) {
471
+ const initialValue = cell.getValue();
472
+ const [value, setValue] = React.useState(initialValue ?? "");
473
+ const cellRef = React.useRef(null);
474
+ const containerRef = React.useRef(null);
475
+ const prevIsEditingRef = React.useRef(false);
476
+ const prevInitialValueRef = React.useRef(initialValue);
477
+ if (initialValue !== prevInitialValueRef.current) {
478
+ prevInitialValueRef.current = initialValue;
479
+ setValue(initialValue ?? "");
480
+ if (cellRef.current && !isEditing) {
481
+ cellRef.current.textContent = initialValue ?? "";
482
+ }
483
+ }
484
+ const onBlur = React.useCallback(() => {
485
+ const currentValue = cellRef.current?.textContent?.trim() ?? "";
486
+ if (!readOnly && currentValue !== initialValue) {
487
+ tableMeta?.onDataUpdate?.({
488
+ rowIndex,
489
+ columnId,
490
+ value: currentValue || null
491
+ });
492
+ }
493
+ tableMeta?.onCellEditingStop?.();
494
+ }, [tableMeta, rowIndex, columnId, initialValue, readOnly]);
495
+ const onInput = React.useCallback((event) => {
496
+ const currentValue = event.currentTarget.textContent ?? "";
497
+ setValue(currentValue);
498
+ }, []);
499
+ const onWrapperKeyDown = React.useCallback(
500
+ (event) => {
501
+ if (isEditing) {
502
+ if (event.key === "Enter") {
503
+ event.preventDefault();
504
+ const currentValue = cellRef.current?.textContent?.trim() ?? "";
505
+ if (!readOnly && currentValue !== initialValue) {
506
+ tableMeta?.onDataUpdate?.({
507
+ rowIndex,
508
+ columnId,
509
+ value: currentValue || null
510
+ });
511
+ }
512
+ tableMeta?.onCellEditingStop?.({ moveToNextRow: true });
513
+ } else if (event.key === "Tab") {
514
+ event.preventDefault();
515
+ const currentValue = cellRef.current?.textContent?.trim() ?? "";
516
+ if (!readOnly && currentValue !== initialValue) {
517
+ tableMeta?.onDataUpdate?.({
518
+ rowIndex,
519
+ columnId,
520
+ value: currentValue || null
521
+ });
522
+ }
523
+ tableMeta?.onCellEditingStop?.({
524
+ direction: event.shiftKey ? "left" : "right"
525
+ });
526
+ } else if (event.key === "Escape") {
527
+ event.preventDefault();
528
+ setValue(initialValue ?? "");
529
+ cellRef.current?.blur();
530
+ }
531
+ } else if (isFocused && !readOnly && event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
532
+ setValue(event.key);
533
+ queueMicrotask(() => {
534
+ if (cellRef.current && cellRef.current.contentEditable === "true") {
535
+ cellRef.current.textContent = event.key;
536
+ const range = document.createRange();
537
+ const selection = window.getSelection();
538
+ range.selectNodeContents(cellRef.current);
539
+ range.collapse(false);
540
+ selection?.removeAllRanges();
541
+ selection?.addRange(range);
542
+ }
543
+ });
544
+ }
545
+ },
546
+ [isEditing, isFocused, initialValue, tableMeta, rowIndex, columnId, readOnly]
547
+ );
548
+ const onLinkClick = React.useCallback(
549
+ (event) => {
550
+ if (isEditing) {
551
+ event.preventDefault();
552
+ return;
553
+ }
554
+ const href = getUrlHref(value);
555
+ if (!href) {
556
+ event.preventDefault();
557
+ toastError(msg.cell.invalidUrlTitle, msg.cell.invalidUrlDescription);
558
+ return;
559
+ }
560
+ event.stopPropagation();
561
+ },
562
+ [isEditing, value]
563
+ );
564
+ React.useEffect(() => {
565
+ const wasEditing = prevIsEditingRef.current;
566
+ prevIsEditingRef.current = isEditing;
567
+ if (isEditing && !wasEditing && cellRef.current) {
568
+ cellRef.current.focus();
569
+ if (!cellRef.current.textContent && value) {
570
+ cellRef.current.textContent = value;
571
+ }
572
+ if (cellRef.current.textContent) {
573
+ const range = document.createRange();
574
+ const selection = window.getSelection();
575
+ range.selectNodeContents(cellRef.current);
576
+ range.collapse(false);
577
+ selection?.removeAllRanges();
578
+ selection?.addRange(range);
579
+ }
580
+ }
581
+ }, [isEditing, value]);
582
+ const displayValue = !isEditing ? value ?? "" : "";
583
+ const urlHref = displayValue ? getUrlHref(displayValue) : "";
584
+ const isDangerousUrl = displayValue && !urlHref;
585
+ return /* @__PURE__ */ jsx(
586
+ DataGridCellWrapper,
587
+ {
588
+ ref: containerRef,
589
+ cell,
590
+ tableMeta,
591
+ rowIndex,
592
+ columnId,
593
+ rowHeight,
594
+ isEditing,
595
+ isFocused,
596
+ isSelected,
597
+ isSearchMatch,
598
+ isActiveSearchMatch,
599
+ readOnly,
600
+ onKeyDown: onWrapperKeyDown,
601
+ children: !isEditing && displayValue ? /* @__PURE__ */ jsx("div", { "data-slot": "grid-cell-content", className: "size-full overflow-hidden", children: /* @__PURE__ */ jsx(
602
+ "a",
603
+ {
604
+ "data-focused": isFocused && !isDangerousUrl ? "" : void 0,
605
+ "data-invalid": isDangerousUrl ? "" : void 0,
606
+ href: urlHref,
607
+ target: "_blank",
608
+ rel: "noopener noreferrer",
609
+ className: "truncate text-link-ink underline decoration-link-ink underline-offset-2 hover:text-link-ink-hover hover:decoration-link-ink-hover data-[invalid]:cursor-not-allowed data-[focused]:text-foreground data-[invalid]:text-destructive-ink data-[focused]:decoration-foreground/50 data-[invalid]:decoration-destructive-ink data-[focused]:hover:decoration-foreground/70 data-[invalid]:hover:decoration-destructive-ink",
610
+ onClick: onLinkClick,
611
+ children: displayValue
612
+ }
613
+ ) }) : /* @__PURE__ */ jsx(
614
+ "div",
615
+ {
616
+ role: "textbox",
617
+ "data-slot": "grid-cell-content",
618
+ contentEditable: isEditing,
619
+ tabIndex: -1,
620
+ ref: cellRef,
621
+ onBlur,
622
+ onInput,
623
+ suppressContentEditableWarning: true,
624
+ className: cn("size-full overflow-hidden outline-none focus-visible:outline-none", {
625
+ "whitespace-nowrap [&_*]:inline [&_*]:whitespace-nowrap [&_br]:hidden": isEditing
626
+ }),
627
+ children: displayValue
628
+ }
629
+ )
630
+ }
631
+ );
632
+ }
633
+ function CheckboxCell({
634
+ cell,
635
+ tableMeta,
636
+ rowIndex,
637
+ columnId,
638
+ rowHeight,
639
+ isFocused,
640
+ isSelected,
641
+ isSearchMatch,
642
+ isActiveSearchMatch,
643
+ readOnly
644
+ }) {
645
+ const initialValue = cell.getValue();
646
+ const [value, setValue] = React.useState(Boolean(initialValue));
647
+ const containerRef = React.useRef(null);
648
+ const prevInitialValueRef = React.useRef(initialValue);
649
+ if (initialValue !== prevInitialValueRef.current) {
650
+ prevInitialValueRef.current = initialValue;
651
+ setValue(Boolean(initialValue));
652
+ }
653
+ const onCheckedChange = React.useCallback(
654
+ (checked) => {
655
+ if (readOnly) return;
656
+ setValue(checked);
657
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: checked });
658
+ },
659
+ [tableMeta, rowIndex, columnId, readOnly]
660
+ );
661
+ const onWrapperKeyDown = React.useCallback(
662
+ (event) => {
663
+ if (isFocused && !readOnly && (event.key === " " || event.key === "Enter")) {
664
+ event.preventDefault();
665
+ event.stopPropagation();
666
+ onCheckedChange(!value);
667
+ } else if (isFocused && event.key === "Tab") {
668
+ event.preventDefault();
669
+ tableMeta?.onCellEditingStop?.({
670
+ direction: event.shiftKey ? "left" : "right"
671
+ });
672
+ }
673
+ },
674
+ [isFocused, value, onCheckedChange, tableMeta, readOnly]
675
+ );
676
+ const onWrapperClick = React.useCallback(
677
+ (event) => {
678
+ if (isFocused && !readOnly) {
679
+ event.preventDefault();
680
+ event.stopPropagation();
681
+ onCheckedChange(!value);
682
+ }
683
+ },
684
+ [isFocused, value, onCheckedChange, readOnly]
685
+ );
686
+ const onCheckboxClick = React.useCallback((event) => {
687
+ event.stopPropagation();
688
+ }, []);
689
+ const onCheckboxMouseDown = React.useCallback((event) => {
690
+ event.stopPropagation();
691
+ }, []);
692
+ const onCheckboxDoubleClick = React.useCallback((event) => {
693
+ event.stopPropagation();
694
+ }, []);
695
+ return /* @__PURE__ */ jsx(
696
+ DataGridCellWrapper,
697
+ {
698
+ ref: containerRef,
699
+ cell,
700
+ tableMeta,
701
+ rowIndex,
702
+ columnId,
703
+ rowHeight,
704
+ isEditing: false,
705
+ isFocused,
706
+ isSelected,
707
+ isSearchMatch,
708
+ isActiveSearchMatch,
709
+ readOnly,
710
+ className: "flex size-full justify-center",
711
+ onClick: onWrapperClick,
712
+ onKeyDown: onWrapperKeyDown,
713
+ children: /* @__PURE__ */ jsx(
714
+ Checkbox,
715
+ {
716
+ checked: value,
717
+ onCheckedChange,
718
+ disabled: readOnly,
719
+ onClick: onCheckboxClick,
720
+ onMouseDown: onCheckboxMouseDown,
721
+ onDoubleClick: onCheckboxDoubleClick
722
+ }
723
+ )
724
+ }
725
+ );
726
+ }
727
+ function SelectCell({
728
+ cell,
729
+ tableMeta,
730
+ rowIndex,
731
+ columnId,
732
+ rowHeight,
733
+ isFocused,
734
+ isEditing,
735
+ isSelected,
736
+ isSearchMatch,
737
+ isActiveSearchMatch,
738
+ readOnly
739
+ }) {
740
+ const initialValue = cell.getValue() ?? void 0;
741
+ const [value, setValue] = React.useState(initialValue);
742
+ const containerRef = React.useRef(null);
743
+ const cellOpts = cell.column.columnDef.meta?.cell;
744
+ const options = React.useMemo(() => cellOpts?.variant === "select" ? cellOpts.options : [], [cellOpts]);
745
+ const optionByValue = React.useMemo(() => new Map(options.map((option) => [option.value, option])), [options]);
746
+ const prevInitialValueRef = React.useRef(initialValue);
747
+ if (initialValue !== prevInitialValueRef.current) {
748
+ prevInitialValueRef.current = initialValue;
749
+ setValue(initialValue);
750
+ }
751
+ const onValueChange = React.useCallback(
752
+ (newValue) => {
753
+ if (readOnly) return;
754
+ setValue(newValue);
755
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValue });
756
+ tableMeta?.onCellEditingStop?.();
757
+ },
758
+ [tableMeta, rowIndex, columnId, readOnly]
759
+ );
760
+ const onOpenChange = React.useCallback(
761
+ (open) => {
762
+ if (open && !readOnly) {
763
+ tableMeta?.onCellEditingStart?.(rowIndex, columnId);
764
+ } else {
765
+ tableMeta?.onCellEditingStop?.();
766
+ }
767
+ },
768
+ [tableMeta, rowIndex, columnId, readOnly]
769
+ );
770
+ const onWrapperKeyDown = React.useCallback(
771
+ (event) => {
772
+ if (isEditing && event.key === "Escape") {
773
+ event.preventDefault();
774
+ setValue(initialValue);
775
+ tableMeta?.onCellEditingStop?.();
776
+ } else if (isFocused && event.key === "Tab") {
777
+ event.preventDefault();
778
+ tableMeta?.onCellEditingStop?.({
779
+ direction: event.shiftKey ? "left" : "right"
780
+ });
781
+ }
782
+ },
783
+ [isEditing, isFocused, initialValue, tableMeta]
784
+ );
785
+ const displayLabel = value ? optionByValue.get(value)?.label ?? value : null;
786
+ return /* @__PURE__ */ jsx(
787
+ DataGridCellWrapper,
788
+ {
789
+ ref: containerRef,
790
+ cell,
791
+ tableMeta,
792
+ rowIndex,
793
+ columnId,
794
+ rowHeight,
795
+ isEditing,
796
+ isFocused,
797
+ isSelected,
798
+ isSearchMatch,
799
+ isActiveSearchMatch,
800
+ readOnly,
801
+ onKeyDown: onWrapperKeyDown,
802
+ children: isEditing ? /* @__PURE__ */ jsxs(Select, { value, onValueChange, open: isEditing, onOpenChange, children: [
803
+ /* @__PURE__ */ jsx(
804
+ SelectTrigger,
805
+ {
806
+ "aria-label": cell.column.columnDef.meta?.label ?? columnId,
807
+ className: "size-full items-start border-none bg-transparent p-0 shadow-none focus:ring-0 focus-visible:ring-0 [&>span]:line-clamp-none [&_svg]:hidden",
808
+ children: displayLabel ? (
809
+ // span (không dùng <Badge> vì Badge render <div>, đặt trong <button> là HTML sai)
810
+ /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ variant: "secondary" }), "whitespace-pre-wrap px-1.5 py-px"), children: /* @__PURE__ */ jsx(SelectValue, {}) })
811
+ ) : /* @__PURE__ */ jsx(SelectValue, {})
812
+ }
813
+ ),
814
+ /* @__PURE__ */ jsx(
815
+ SelectContent,
816
+ {
817
+ "data-grid-cell-editor": "",
818
+ align: "start",
819
+ alignOffset: -8,
820
+ sideOffset: -8,
821
+ className: "min-w-[calc(var(--radix-select-trigger-width)+16px)]",
822
+ children: /* @__PURE__ */ jsx(SelectGroup, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
823
+ }
824
+ )
825
+ ] }) : displayLabel ? /* @__PURE__ */ jsx(Badge, { "data-slot": "grid-cell-content", variant: "secondary", className: "whitespace-pre-wrap px-1.5 py-px", children: displayLabel }) : null
826
+ }
827
+ );
828
+ }
829
+ function MultiSelectCell({
830
+ cell,
831
+ tableMeta,
832
+ rowIndex,
833
+ columnId,
834
+ rowHeight,
835
+ isFocused,
836
+ isEditing,
837
+ isSelected,
838
+ isSearchMatch,
839
+ isActiveSearchMatch,
840
+ readOnly
841
+ }) {
842
+ const cellValue = React.useMemo(() => {
843
+ const value = cell.getValue();
844
+ return value ?? [];
845
+ }, [cell]);
846
+ const cellKey = getCellKey(rowIndex, columnId);
847
+ const prevCellKeyRef = React.useRef(cellKey);
848
+ const [selectedValues, setSelectedValues] = React.useState(cellValue);
849
+ const [searchValue, setSearchValue] = React.useState("");
850
+ const containerRef = React.useRef(null);
851
+ const inputRef = React.useRef(null);
852
+ const cellOpts = cell.column.columnDef.meta?.cell;
853
+ const options = React.useMemo(() => cellOpts?.variant === "multi-select" ? cellOpts.options : [], [cellOpts]);
854
+ const optionByValue = React.useMemo(() => new Map(options.map((option) => [option.value, option])), [options]);
855
+ const sideOffset = -(containerRef.current?.clientHeight ?? 0);
856
+ const prevCellValueRef = React.useRef(cellValue);
857
+ if (cellValue !== prevCellValueRef.current) {
858
+ prevCellValueRef.current = cellValue;
859
+ setSelectedValues(cellValue);
860
+ }
861
+ if (prevCellKeyRef.current !== cellKey) {
862
+ prevCellKeyRef.current = cellKey;
863
+ setSearchValue("");
864
+ }
865
+ const onValueChange = React.useCallback(
866
+ (value) => {
867
+ if (readOnly) return;
868
+ let newValues = [];
869
+ setSelectedValues((curr) => {
870
+ newValues = curr.includes(value) ? curr.filter((v) => v !== value) : [...curr, value];
871
+ return newValues;
872
+ });
873
+ queueMicrotask(() => {
874
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValues });
875
+ inputRef.current?.focus();
876
+ });
877
+ setSearchValue("");
878
+ },
879
+ [tableMeta, rowIndex, columnId, readOnly]
880
+ );
881
+ const removeValue = React.useCallback(
882
+ (valueToRemove, event) => {
883
+ if (readOnly) return;
884
+ event?.stopPropagation();
885
+ event?.preventDefault();
886
+ let newValues = [];
887
+ setSelectedValues((curr) => {
888
+ newValues = curr.filter((v) => v !== valueToRemove);
889
+ return newValues;
890
+ });
891
+ queueMicrotask(() => {
892
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValues });
893
+ inputRef.current?.focus();
894
+ });
895
+ },
896
+ [tableMeta, rowIndex, columnId, readOnly]
897
+ );
898
+ const clearAll = React.useCallback(() => {
899
+ if (readOnly) return;
900
+ setSelectedValues([]);
901
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: [] });
902
+ queueMicrotask(() => inputRef.current?.focus());
903
+ }, [tableMeta, rowIndex, columnId, readOnly]);
904
+ const onOpenChange = React.useCallback(
905
+ (open) => {
906
+ if (open && !readOnly) {
907
+ tableMeta?.onCellEditingStart?.(rowIndex, columnId);
908
+ } else {
909
+ setSearchValue("");
910
+ tableMeta?.onCellEditingStop?.();
911
+ }
912
+ },
913
+ [tableMeta, rowIndex, columnId, readOnly]
914
+ );
915
+ const onOpenAutoFocus = React.useCallback((event) => {
916
+ event.preventDefault();
917
+ inputRef.current?.focus();
918
+ }, []);
919
+ const onWrapperKeyDown = React.useCallback(
920
+ (event) => {
921
+ if (isEditing && event.key === "Escape") {
922
+ event.preventDefault();
923
+ setSelectedValues(cellValue);
924
+ setSearchValue("");
925
+ tableMeta?.onCellEditingStop?.();
926
+ } else if (isFocused && event.key === "Tab") {
927
+ event.preventDefault();
928
+ setSearchValue("");
929
+ tableMeta?.onCellEditingStop?.({
930
+ direction: event.shiftKey ? "left" : "right"
931
+ });
932
+ }
933
+ },
934
+ [isEditing, isFocused, cellValue, tableMeta]
935
+ );
936
+ const onInputKeyDown = React.useCallback(
937
+ (event) => {
938
+ if (event.key === "Backspace" && searchValue === "") {
939
+ event.preventDefault();
940
+ let newValues = null;
941
+ setSelectedValues((curr) => {
942
+ if (curr.length === 0) return curr;
943
+ newValues = curr.slice(0, -1);
944
+ return newValues;
945
+ });
946
+ queueMicrotask(() => {
947
+ if (newValues !== null) {
948
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValues });
949
+ }
950
+ inputRef.current?.focus();
951
+ });
952
+ }
953
+ if (event.key === "Escape") {
954
+ event.stopPropagation();
955
+ }
956
+ },
957
+ [searchValue, tableMeta, rowIndex, columnId]
958
+ );
959
+ const displayLabels = selectedValues.map((val) => optionByValue.get(val)?.label ?? val).filter(Boolean);
960
+ const selectedValuesSet = React.useMemo(() => new Set(selectedValues), [selectedValues]);
961
+ const lineCount = getLineCount(rowHeight);
962
+ const { visibleItems: visibleLabels, hiddenCount: hiddenBadgeCount } = useBadgeOverflow({
963
+ items: displayLabels,
964
+ getLabel: (label) => label,
965
+ containerRef,
966
+ lineCount
967
+ });
968
+ return /* @__PURE__ */ jsxs(
969
+ DataGridCellWrapper,
970
+ {
971
+ ref: containerRef,
972
+ cell,
973
+ tableMeta,
974
+ rowIndex,
975
+ columnId,
976
+ rowHeight,
977
+ isEditing,
978
+ isFocused,
979
+ isSelected,
980
+ isSearchMatch,
981
+ isActiveSearchMatch,
982
+ readOnly,
983
+ onKeyDown: onWrapperKeyDown,
984
+ children: [
985
+ isEditing ? /* @__PURE__ */ jsxs(Popover, { open: isEditing, onOpenChange, children: [
986
+ /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0" }) }),
987
+ /* @__PURE__ */ jsx(
988
+ PopoverContent,
989
+ {
990
+ "data-grid-cell-editor": "",
991
+ align: "start",
992
+ sideOffset,
993
+ className: "w-[300px] rounded-none p-0",
994
+ onOpenAutoFocus,
995
+ children: /* @__PURE__ */ jsxs(Command, { className: "[&_[data-slot=command-input-wrapper]]:h-auto [&_[data-slot=command-input-wrapper]]:gap-0 [&_[data-slot=command-input-wrapper]]:border-none [&_[data-slot=command-input-wrapper]]:p-0 [&_[data-slot=command-input-wrapper]_svg]:hidden", children: [
996
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-9 flex-wrap items-center gap-1 border-b px-3 py-1.5", children: [
997
+ selectedValues.map((value) => {
998
+ const label = optionByValue.get(value)?.label ?? value;
999
+ return /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "gap-1 px-1.5 py-px", children: [
1000
+ label,
1001
+ /* @__PURE__ */ jsx(
1002
+ "button",
1003
+ {
1004
+ type: "button",
1005
+ "aria-label": `${msg.cell.clearAll}: ${label}`,
1006
+ onClick: (event) => removeValue(value, event),
1007
+ onPointerDown: (event) => {
1008
+ event.preventDefault();
1009
+ event.stopPropagation();
1010
+ },
1011
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-3" })
1012
+ }
1013
+ )
1014
+ ] }, value);
1015
+ }),
1016
+ /* @__PURE__ */ jsx(
1017
+ CommandInput,
1018
+ {
1019
+ ref: inputRef,
1020
+ value: searchValue,
1021
+ onValueChange: setSearchValue,
1022
+ onKeyDown: onInputKeyDown,
1023
+ placeholder: msg.cell.optionSearchPlaceholder,
1024
+ "aria-label": msg.cell.optionSearchPlaceholder,
1025
+ className: "h-auto flex-1 p-0"
1026
+ }
1027
+ )
1028
+ ] }),
1029
+ /* @__PURE__ */ jsxs(CommandList, { className: "max-h-full", children: [
1030
+ /* @__PURE__ */ jsx(CommandEmpty, { children: msg.cell.optionEmpty }),
1031
+ /* @__PURE__ */ jsx(CommandGroup, { className: "max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden", children: options.map((option) => {
1032
+ const isSelected2 = selectedValuesSet.has(option.value);
1033
+ return /* @__PURE__ */ jsxs(CommandItem, { value: option.label, onSelect: () => onValueChange(option.value), children: [
1034
+ /* @__PURE__ */ jsx(
1035
+ "div",
1036
+ {
1037
+ className: cn(
1038
+ "flex size-4 items-center justify-center rounded-sm border border-choice-edge",
1039
+ isSelected2 ? "border-control-on bg-control-on text-control-check" : "[&_svg]:invisible"
1040
+ ),
1041
+ children: /* @__PURE__ */ jsx(Check, { className: "size-3" })
1042
+ }
1043
+ ),
1044
+ /* @__PURE__ */ jsx("span", { children: option.label })
1045
+ ] }, option.value);
1046
+ }) }),
1047
+ selectedValues.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1048
+ /* @__PURE__ */ jsx(CommandSeparator, {}),
1049
+ /* @__PURE__ */ jsx(CommandGroup, { children: /* @__PURE__ */ jsx(CommandItem, { onSelect: clearAll, className: "justify-center text-muted-foreground", children: msg.cell.clearAll }) })
1050
+ ] })
1051
+ ] })
1052
+ ] })
1053
+ }
1054
+ )
1055
+ ] }) : null,
1056
+ displayLabels.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 overflow-hidden", children: [
1057
+ visibleLabels.map((label, index) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "px-1.5 py-px", children: label }, selectedValues[index])),
1058
+ hiddenBadgeCount > 0 && /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: "px-1.5 py-px text-muted-foreground", children: [
1059
+ "+",
1060
+ hiddenBadgeCount
1061
+ ] })
1062
+ ] }) : null
1063
+ ]
1064
+ }
1065
+ );
1066
+ }
1067
+ function DateCell({
1068
+ cell,
1069
+ tableMeta,
1070
+ rowIndex,
1071
+ columnId,
1072
+ rowHeight,
1073
+ isFocused,
1074
+ isEditing,
1075
+ isSelected,
1076
+ isSearchMatch,
1077
+ isActiveSearchMatch,
1078
+ readOnly
1079
+ }) {
1080
+ const initialValue = cell.getValue();
1081
+ const [value, setValue] = React.useState(initialValue ?? "");
1082
+ const containerRef = React.useRef(null);
1083
+ const prevInitialValueRef = React.useRef(initialValue);
1084
+ if (initialValue !== prevInitialValueRef.current) {
1085
+ prevInitialValueRef.current = initialValue;
1086
+ setValue(initialValue ?? "");
1087
+ }
1088
+ const selectedDate = value ? parseLocalDate(value) ?? void 0 : void 0;
1089
+ const onDateSelect = React.useCallback(
1090
+ (date) => {
1091
+ if (!date || readOnly) return;
1092
+ const formattedDate = formatDateToString(date);
1093
+ setValue(formattedDate);
1094
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: formattedDate });
1095
+ tableMeta?.onCellEditingStop?.();
1096
+ },
1097
+ [tableMeta, rowIndex, columnId, readOnly]
1098
+ );
1099
+ const onOpenChange = React.useCallback(
1100
+ (open) => {
1101
+ if (open && !readOnly) {
1102
+ tableMeta?.onCellEditingStart?.(rowIndex, columnId);
1103
+ } else {
1104
+ tableMeta?.onCellEditingStop?.();
1105
+ }
1106
+ },
1107
+ [tableMeta, rowIndex, columnId, readOnly]
1108
+ );
1109
+ const onWrapperKeyDown = React.useCallback(
1110
+ (event) => {
1111
+ if (isEditing && event.key === "Escape") {
1112
+ event.preventDefault();
1113
+ setValue(initialValue);
1114
+ tableMeta?.onCellEditingStop?.();
1115
+ } else if (isFocused && event.key === "Tab") {
1116
+ event.preventDefault();
1117
+ tableMeta?.onCellEditingStop?.({
1118
+ direction: event.shiftKey ? "left" : "right"
1119
+ });
1120
+ }
1121
+ },
1122
+ [isEditing, isFocused, initialValue, tableMeta]
1123
+ );
1124
+ return /* @__PURE__ */ jsx(
1125
+ DataGridCellWrapper,
1126
+ {
1127
+ ref: containerRef,
1128
+ cell,
1129
+ tableMeta,
1130
+ rowIndex,
1131
+ columnId,
1132
+ rowHeight,
1133
+ isEditing,
1134
+ isFocused,
1135
+ isSelected,
1136
+ isSearchMatch,
1137
+ isActiveSearchMatch,
1138
+ readOnly,
1139
+ onKeyDown: onWrapperKeyDown,
1140
+ children: /* @__PURE__ */ jsxs(Popover, { open: isEditing, onOpenChange, children: [
1141
+ /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx("span", { "data-slot": "grid-cell-content", children: formatDateForDisplay(value) }) }),
1142
+ isEditing && /* @__PURE__ */ jsx(PopoverContent, { "data-grid-cell-editor": "", align: "start", alignOffset: -8, className: "w-auto p-0", children: /* @__PURE__ */ jsx(
1143
+ Calendar,
1144
+ {
1145
+ mode: "single",
1146
+ locale: vi,
1147
+ autoFocus: true,
1148
+ defaultMonth: selectedDate ?? /* @__PURE__ */ new Date(),
1149
+ selected: selectedDate,
1150
+ onSelect: (date) => {
1151
+ if (date) onDateSelect(date);
1152
+ }
1153
+ }
1154
+ ) })
1155
+ ] })
1156
+ }
1157
+ );
1158
+ }
1159
+ function FileCell({
1160
+ cell,
1161
+ tableMeta,
1162
+ rowIndex,
1163
+ columnId,
1164
+ rowHeight,
1165
+ isFocused,
1166
+ isEditing,
1167
+ isSelected,
1168
+ isSearchMatch,
1169
+ isActiveSearchMatch,
1170
+ readOnly
1171
+ }) {
1172
+ const cellValue = React.useMemo(() => cell.getValue() ?? [], [cell]);
1173
+ const cellKey = getCellKey(rowIndex, columnId);
1174
+ const prevCellKeyRef = React.useRef(cellKey);
1175
+ const labelId = React.useId();
1176
+ const descriptionId = React.useId();
1177
+ const [files, setFiles] = React.useState(cellValue);
1178
+ const [uploadingFiles, setUploadingFiles] = React.useState(/* @__PURE__ */ new Set());
1179
+ const [deletingFiles, setDeletingFiles] = React.useState(/* @__PURE__ */ new Set());
1180
+ const [isDraggingOver, setIsDraggingOver] = React.useState(false);
1181
+ const [isDragging, setIsDragging] = React.useState(false);
1182
+ const [error, setError] = React.useState(null);
1183
+ const isUploading = uploadingFiles.size > 0;
1184
+ const isDeleting = deletingFiles.size > 0;
1185
+ const isPending = isUploading || isDeleting;
1186
+ const containerRef = React.useRef(null);
1187
+ const fileInputRef = React.useRef(null);
1188
+ const dropzoneRef = React.useRef(null);
1189
+ const cellOpts = cell.column.columnDef.meta?.cell;
1190
+ const sideOffset = -(containerRef.current?.clientHeight ?? 0);
1191
+ const fileCellOpts = cellOpts?.variant === "file" ? cellOpts : null;
1192
+ const maxFileSize = fileCellOpts?.maxFileSize ?? 10 * 1024 * 1024;
1193
+ const maxFiles = fileCellOpts?.maxFiles ?? 10;
1194
+ const accept = fileCellOpts?.accept;
1195
+ const multiple = fileCellOpts?.multiple ?? false;
1196
+ const acceptedTypes = React.useMemo(() => accept ? accept.split(",").map((t) => t.trim()) : null, [accept]);
1197
+ const prevCellValueRef = React.useRef(cellValue);
1198
+ if (cellValue !== prevCellValueRef.current) {
1199
+ prevCellValueRef.current = cellValue;
1200
+ for (const file of files) {
1201
+ if (file.url) {
1202
+ URL.revokeObjectURL(file.url);
1203
+ }
1204
+ }
1205
+ setFiles(cellValue);
1206
+ setError(null);
1207
+ }
1208
+ if (prevCellKeyRef.current !== cellKey) {
1209
+ prevCellKeyRef.current = cellKey;
1210
+ setError(null);
1211
+ }
1212
+ const validateFile = React.useCallback(
1213
+ (file) => {
1214
+ if (maxFileSize && file.size > maxFileSize) {
1215
+ return msg.file.tooLarge(formatFileSize(maxFileSize));
1216
+ }
1217
+ if (acceptedTypes) {
1218
+ const fileExtension = `.${file.name.split(".").pop()}`;
1219
+ const isAccepted = acceptedTypes.some((type) => {
1220
+ if (type.endsWith("/*")) {
1221
+ const baseType = type.slice(0, -2);
1222
+ return file.type.startsWith(`${baseType}/`);
1223
+ }
1224
+ if (type.startsWith(".")) {
1225
+ return fileExtension.toLowerCase() === type.toLowerCase();
1226
+ }
1227
+ return file.type === type;
1228
+ });
1229
+ if (!isAccepted) {
1230
+ return msg.file.wrongType;
1231
+ }
1232
+ }
1233
+ return null;
1234
+ },
1235
+ [maxFileSize, acceptedTypes]
1236
+ );
1237
+ const addFiles = React.useCallback(
1238
+ async (newFiles, skipUpload = false) => {
1239
+ if (readOnly || isPending) return;
1240
+ setError(null);
1241
+ if (maxFiles && files.length + newFiles.length > maxFiles) {
1242
+ const errorMessage = msg.file.tooMany(maxFiles);
1243
+ setError(errorMessage);
1244
+ toastMessage(errorMessage);
1245
+ setTimeout(() => {
1246
+ setError(null);
1247
+ }, 2e3);
1248
+ return;
1249
+ }
1250
+ const rejectedFiles = [];
1251
+ const filesToValidate = [];
1252
+ for (const file of newFiles) {
1253
+ const validationError = validateFile(file);
1254
+ if (validationError) {
1255
+ rejectedFiles.push({ name: file.name, reason: validationError });
1256
+ continue;
1257
+ }
1258
+ filesToValidate.push(file);
1259
+ }
1260
+ if (rejectedFiles.length > 0) {
1261
+ const firstError = rejectedFiles[0];
1262
+ if (firstError) {
1263
+ setError(firstError.reason);
1264
+ const truncatedName = firstError.name.length > 20 ? `${firstError.name.slice(0, 20)}...` : firstError.name;
1265
+ if (rejectedFiles.length === 1) {
1266
+ toastMessage(firstError.reason, msg.file.rejectedOne(truncatedName));
1267
+ } else {
1268
+ toastMessage(firstError.reason, msg.file.rejectedMany(truncatedName, rejectedFiles.length - 1));
1269
+ }
1270
+ setTimeout(() => {
1271
+ setError(null);
1272
+ }, 2e3);
1273
+ }
1274
+ }
1275
+ if (filesToValidate.length > 0) {
1276
+ if (!skipUpload) {
1277
+ const tempFiles = filesToValidate.map((f) => ({
1278
+ id: generateFileId(),
1279
+ name: f.name,
1280
+ size: f.size,
1281
+ type: f.type,
1282
+ url: void 0
1283
+ }));
1284
+ const filesWithTemp = [...files, ...tempFiles];
1285
+ setFiles(filesWithTemp);
1286
+ const uploadingIds = new Set(tempFiles.map((f) => f.id));
1287
+ setUploadingFiles(uploadingIds);
1288
+ let uploadedFiles = [];
1289
+ if (tableMeta?.onFilesUpload) {
1290
+ try {
1291
+ uploadedFiles = await tableMeta.onFilesUpload({
1292
+ files: filesToValidate,
1293
+ rowIndex,
1294
+ columnId
1295
+ });
1296
+ } catch (error2) {
1297
+ toastError(error2 instanceof Error ? error2.message : msg.file.uploadFailed(filesToValidate.length));
1298
+ setFiles((prev) => prev.filter((f) => !uploadingIds.has(f.id)));
1299
+ setUploadingFiles(/* @__PURE__ */ new Set());
1300
+ return;
1301
+ }
1302
+ } else {
1303
+ uploadedFiles = filesToValidate.map((f, i) => ({
1304
+ id: tempFiles[i]?.id ?? generateFileId(),
1305
+ name: f.name,
1306
+ size: f.size,
1307
+ type: f.type,
1308
+ url: URL.createObjectURL(f)
1309
+ }));
1310
+ }
1311
+ const finalFiles = filesWithTemp.map((f) => {
1312
+ if (uploadingIds.has(f.id)) {
1313
+ return uploadedFiles.find((uf) => uf.name === f.name) ?? f;
1314
+ }
1315
+ return f;
1316
+ }).filter((f) => f.url !== void 0);
1317
+ setFiles(finalFiles);
1318
+ setUploadingFiles(/* @__PURE__ */ new Set());
1319
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: finalFiles });
1320
+ } else {
1321
+ const newFilesData = filesToValidate.map((f) => ({
1322
+ id: generateFileId(),
1323
+ name: f.name,
1324
+ size: f.size,
1325
+ type: f.type,
1326
+ url: URL.createObjectURL(f)
1327
+ }));
1328
+ const updatedFiles = [...files, ...newFilesData];
1329
+ setFiles(updatedFiles);
1330
+ tableMeta?.onDataUpdate?.({
1331
+ rowIndex,
1332
+ columnId,
1333
+ value: updatedFiles
1334
+ });
1335
+ }
1336
+ }
1337
+ },
1338
+ [files, maxFiles, validateFile, tableMeta, rowIndex, columnId, readOnly, isPending]
1339
+ );
1340
+ const removeFile = React.useCallback(
1341
+ async (fileId) => {
1342
+ if (readOnly || isPending) return;
1343
+ setError(null);
1344
+ const fileToRemove = files.find((f) => f.id === fileId);
1345
+ if (!fileToRemove) return;
1346
+ setDeletingFiles((prev) => new Set(prev).add(fileId));
1347
+ if (tableMeta?.onFilesDelete) {
1348
+ try {
1349
+ await tableMeta.onFilesDelete({
1350
+ fileIds: [fileId],
1351
+ rowIndex,
1352
+ columnId
1353
+ });
1354
+ } catch (error2) {
1355
+ toastError(error2 instanceof Error ? error2.message : msg.file.deleteFailed(fileToRemove.name));
1356
+ setDeletingFiles((prev) => {
1357
+ const next = new Set(prev);
1358
+ next.delete(fileId);
1359
+ return next;
1360
+ });
1361
+ return;
1362
+ }
1363
+ }
1364
+ if (fileToRemove.url?.startsWith("blob:")) {
1365
+ URL.revokeObjectURL(fileToRemove.url);
1366
+ }
1367
+ const updatedFiles = files.filter((f) => f.id !== fileId);
1368
+ setFiles(updatedFiles);
1369
+ setDeletingFiles((prev) => {
1370
+ const next = new Set(prev);
1371
+ next.delete(fileId);
1372
+ return next;
1373
+ });
1374
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: updatedFiles });
1375
+ },
1376
+ [files, tableMeta, rowIndex, columnId, readOnly, isPending]
1377
+ );
1378
+ const clearAll = React.useCallback(async () => {
1379
+ if (readOnly || isPending) return;
1380
+ setError(null);
1381
+ const fileIds = files.map((f) => f.id);
1382
+ setDeletingFiles(new Set(fileIds));
1383
+ if (tableMeta?.onFilesDelete && files.length > 0) {
1384
+ try {
1385
+ await tableMeta.onFilesDelete({
1386
+ fileIds,
1387
+ rowIndex,
1388
+ columnId
1389
+ });
1390
+ } catch (error2) {
1391
+ toastError(error2 instanceof Error ? error2.message : msg.file.deleteFailedMany);
1392
+ setDeletingFiles(/* @__PURE__ */ new Set());
1393
+ return;
1394
+ }
1395
+ }
1396
+ for (const file of files) {
1397
+ if (file.url?.startsWith("blob:")) {
1398
+ URL.revokeObjectURL(file.url);
1399
+ }
1400
+ }
1401
+ setFiles([]);
1402
+ setDeletingFiles(/* @__PURE__ */ new Set());
1403
+ tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: [] });
1404
+ }, [files, tableMeta, rowIndex, columnId, readOnly, isPending]);
1405
+ const onCellDragEnter = React.useCallback((event) => {
1406
+ event.preventDefault();
1407
+ event.stopPropagation();
1408
+ if (event.dataTransfer.types.includes("Files")) {
1409
+ setIsDraggingOver(true);
1410
+ }
1411
+ }, []);
1412
+ const onCellDragLeave = React.useCallback((event) => {
1413
+ event.preventDefault();
1414
+ event.stopPropagation();
1415
+ const rect = event.currentTarget.getBoundingClientRect();
1416
+ const x = event.clientX;
1417
+ const y = event.clientY;
1418
+ if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
1419
+ setIsDraggingOver(false);
1420
+ }
1421
+ }, []);
1422
+ const onCellDragOver = React.useCallback((event) => {
1423
+ event.preventDefault();
1424
+ event.stopPropagation();
1425
+ }, []);
1426
+ const onCellDrop = React.useCallback(
1427
+ (event) => {
1428
+ event.preventDefault();
1429
+ event.stopPropagation();
1430
+ setIsDraggingOver(false);
1431
+ const droppedFiles = Array.from(event.dataTransfer.files);
1432
+ if (droppedFiles.length > 0) {
1433
+ addFiles(droppedFiles, false);
1434
+ }
1435
+ },
1436
+ [addFiles]
1437
+ );
1438
+ const onDropzoneDragEnter = React.useCallback((event) => {
1439
+ event.preventDefault();
1440
+ event.stopPropagation();
1441
+ setIsDragging(true);
1442
+ }, []);
1443
+ const onDropzoneDragLeave = React.useCallback((event) => {
1444
+ event.preventDefault();
1445
+ event.stopPropagation();
1446
+ const rect = event.currentTarget.getBoundingClientRect();
1447
+ const x = event.clientX;
1448
+ const y = event.clientY;
1449
+ if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
1450
+ setIsDragging(false);
1451
+ }
1452
+ }, []);
1453
+ const onDropzoneDragOver = React.useCallback((event) => {
1454
+ event.preventDefault();
1455
+ event.stopPropagation();
1456
+ }, []);
1457
+ const onDropzoneDrop = React.useCallback(
1458
+ (event) => {
1459
+ event.preventDefault();
1460
+ event.stopPropagation();
1461
+ setIsDragging(false);
1462
+ const droppedFiles = Array.from(event.dataTransfer.files);
1463
+ addFiles(droppedFiles, false);
1464
+ },
1465
+ [addFiles]
1466
+ );
1467
+ const onDropzoneClick = React.useCallback(() => {
1468
+ fileInputRef.current?.click();
1469
+ }, []);
1470
+ const onDropzoneKeyDown = React.useCallback(
1471
+ (event) => {
1472
+ if (event.key === "Enter" || event.key === " ") {
1473
+ event.preventDefault();
1474
+ onDropzoneClick();
1475
+ }
1476
+ },
1477
+ [onDropzoneClick]
1478
+ );
1479
+ const onFileInputChange = React.useCallback(
1480
+ (event) => {
1481
+ const selectedFiles = Array.from(event.target.files ?? []);
1482
+ addFiles(selectedFiles, false);
1483
+ event.target.value = "";
1484
+ },
1485
+ [addFiles]
1486
+ );
1487
+ const onOpenChange = React.useCallback(
1488
+ (open) => {
1489
+ if (open && !readOnly) {
1490
+ setError(null);
1491
+ tableMeta?.onCellEditingStart?.(rowIndex, columnId);
1492
+ } else {
1493
+ setError(null);
1494
+ tableMeta?.onCellEditingStop?.();
1495
+ }
1496
+ },
1497
+ [tableMeta, rowIndex, columnId, readOnly]
1498
+ );
1499
+ const onEscapeKeyDown = React.useCallback((event) => {
1500
+ event.stopPropagation();
1501
+ }, []);
1502
+ const onOpenAutoFocus = React.useCallback((event) => {
1503
+ event.preventDefault();
1504
+ queueMicrotask(() => {
1505
+ dropzoneRef.current?.focus();
1506
+ });
1507
+ }, []);
1508
+ const onWrapperKeyDown = React.useCallback(
1509
+ (event) => {
1510
+ if (isEditing) {
1511
+ if (event.key === "Escape") {
1512
+ event.preventDefault();
1513
+ setFiles(cellValue);
1514
+ setError(null);
1515
+ tableMeta?.onCellEditingStop?.();
1516
+ } else if (event.key === " ") {
1517
+ event.preventDefault();
1518
+ onDropzoneClick();
1519
+ } else if (event.key === "Tab") {
1520
+ event.preventDefault();
1521
+ tableMeta?.onCellEditingStop?.({
1522
+ direction: event.shiftKey ? "left" : "right"
1523
+ });
1524
+ }
1525
+ } else if (isFocused && event.key === "Enter") {
1526
+ event.preventDefault();
1527
+ tableMeta?.onCellEditingStart?.(rowIndex, columnId);
1528
+ } else if (isFocused && event.key === "Tab") {
1529
+ event.preventDefault();
1530
+ tableMeta?.onCellEditingStop?.({
1531
+ direction: event.shiftKey ? "left" : "right"
1532
+ });
1533
+ }
1534
+ },
1535
+ [isEditing, isFocused, cellValue, tableMeta, onDropzoneClick, rowIndex, columnId]
1536
+ );
1537
+ React.useEffect(() => {
1538
+ return () => {
1539
+ for (const file of files) {
1540
+ if (file.url) {
1541
+ URL.revokeObjectURL(file.url);
1542
+ }
1543
+ }
1544
+ };
1545
+ }, [files]);
1546
+ const lineCount = getLineCount(rowHeight);
1547
+ const { visibleItems: visibleFiles, hiddenCount: hiddenFileCount } = useBadgeOverflow({
1548
+ items: files,
1549
+ getLabel: (file) => file.name,
1550
+ containerRef,
1551
+ lineCount,
1552
+ cacheKeyPrefix: "file",
1553
+ iconSize: 12,
1554
+ maxWidth: 100
1555
+ });
1556
+ return /* @__PURE__ */ jsxs(
1557
+ DataGridCellWrapper,
1558
+ {
1559
+ ref: containerRef,
1560
+ cell,
1561
+ tableMeta,
1562
+ rowIndex,
1563
+ columnId,
1564
+ rowHeight,
1565
+ isEditing,
1566
+ isFocused,
1567
+ isSelected,
1568
+ isSearchMatch,
1569
+ isActiveSearchMatch,
1570
+ readOnly,
1571
+ className: cn({
1572
+ "ring-1 ring-primary/80 ring-inset": isDraggingOver
1573
+ }),
1574
+ onDragEnter: onCellDragEnter,
1575
+ onDragLeave: onCellDragLeave,
1576
+ onDragOver: onCellDragOver,
1577
+ onDrop: onCellDrop,
1578
+ onKeyDown: onWrapperKeyDown,
1579
+ children: [
1580
+ isEditing ? /* @__PURE__ */ jsxs(Popover, { open: isEditing, onOpenChange, children: [
1581
+ /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0" }) }),
1582
+ /* @__PURE__ */ jsx(
1583
+ PopoverContent,
1584
+ {
1585
+ "data-grid-cell-editor": "",
1586
+ align: "start",
1587
+ sideOffset,
1588
+ className: "w-[400px] rounded-none p-0",
1589
+ onEscapeKeyDown,
1590
+ onOpenAutoFocus,
1591
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-3", children: [
1592
+ /* @__PURE__ */ jsx("span", { id: labelId, className: "sr-only", children: msg.file.uploadLabel }),
1593
+ /* @__PURE__ */ jsxs(
1594
+ "div",
1595
+ {
1596
+ role: "region",
1597
+ "aria-labelledby": labelId,
1598
+ "aria-describedby": descriptionId,
1599
+ "aria-invalid": !!error,
1600
+ "aria-disabled": isPending,
1601
+ "data-dragging": isDragging ? "" : void 0,
1602
+ "data-invalid": error ? "" : void 0,
1603
+ "data-disabled": isPending ? "" : void 0,
1604
+ tabIndex: isDragging || isPending ? -1 : 0,
1605
+ className: "flex cursor-pointer flex-col items-center justify-center gap-2 rounded-md border-2 border-dashed p-6 outline-none transition-colors hover:bg-accent/30 focus-visible:border-primary-ink focus-visible:outline-none data-[disabled]:pointer-events-none data-[dragging]:border-primary-ink data-[invalid]:border-destructive-ink data-[dragging]:bg-accent/30 data-[disabled]:opacity-50",
1606
+ ref: dropzoneRef,
1607
+ onClick: onDropzoneClick,
1608
+ onDragEnter: onDropzoneDragEnter,
1609
+ onDragLeave: onDropzoneDragLeave,
1610
+ onDragOver: onDropzoneDragOver,
1611
+ onDrop: onDropzoneDrop,
1612
+ onKeyDown: onDropzoneKeyDown,
1613
+ children: [
1614
+ /* @__PURE__ */ jsx(Upload, { "aria-hidden": "true", className: "size-8 text-muted-foreground" }),
1615
+ /* @__PURE__ */ jsxs("div", { className: "text-center text-sm", children: [
1616
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: isDragging ? msg.file.dropHere : msg.file.dragHere }),
1617
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: msg.file.browse })
1618
+ ] }),
1619
+ /* @__PURE__ */ jsx("p", { id: descriptionId, className: "text-muted-foreground text-xs", children: msg.file.limits(maxFileSize ? formatFileSize(maxFileSize) : void 0, maxFiles) })
1620
+ ]
1621
+ }
1622
+ ),
1623
+ /* @__PURE__ */ jsx(
1624
+ "input",
1625
+ {
1626
+ type: "file",
1627
+ "aria-labelledby": labelId,
1628
+ "aria-describedby": descriptionId,
1629
+ multiple,
1630
+ accept,
1631
+ className: "sr-only",
1632
+ ref: fileInputRef,
1633
+ onChange: onFileInputChange
1634
+ }
1635
+ ),
1636
+ files.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1637
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1638
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-muted-foreground text-xs", children: msg.file.count(files.length) }),
1639
+ /* @__PURE__ */ jsx(
1640
+ Button,
1641
+ {
1642
+ type: "button",
1643
+ variant: "ghost",
1644
+ className: "h-6 px-2 text-muted-foreground text-xs",
1645
+ onClick: clearAll,
1646
+ disabled: isPending,
1647
+ children: msg.file.clearAll
1648
+ }
1649
+ )
1650
+ ] }),
1651
+ /* @__PURE__ */ jsx("div", { className: "max-h-[200px] space-y-1 overflow-y-auto", children: files.map((file) => {
1652
+ const FileIcon = getFileIcon(file.type);
1653
+ const isFileUploading = uploadingFiles.has(file.id);
1654
+ const isFileDeleting = deletingFiles.has(file.id);
1655
+ const isFilePending = isFileUploading || isFileDeleting;
1656
+ return /* @__PURE__ */ jsxs(
1657
+ "div",
1658
+ {
1659
+ "data-pending": isFilePending ? "" : void 0,
1660
+ className: "flex items-center gap-2 rounded-md border bg-muted/50 px-2 py-1.5 data-[pending]:opacity-60",
1661
+ children: [
1662
+ FileIcon && /* @__PURE__ */ jsx(FileIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
1663
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden", children: [
1664
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: file.name }),
1665
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: isFileUploading ? msg.file.uploading : isFileDeleting ? msg.file.deleting : formatFileSize(file.size) })
1666
+ ] }),
1667
+ /* @__PURE__ */ jsx(
1668
+ Button,
1669
+ {
1670
+ type: "button",
1671
+ "aria-label": `${msg.file.remove}: ${file.name}`,
1672
+ variant: "ghost",
1673
+ size: "icon",
1674
+ className: "size-5 rounded-sm",
1675
+ onClick: () => removeFile(file.id),
1676
+ disabled: isPending,
1677
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-3" })
1678
+ }
1679
+ )
1680
+ ]
1681
+ },
1682
+ file.id
1683
+ );
1684
+ }) })
1685
+ ] })
1686
+ ] })
1687
+ }
1688
+ )
1689
+ ] }) : null,
1690
+ isDraggingOver ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 text-primary-ink text-sm", children: [
1691
+ /* @__PURE__ */ jsx(Upload, { "aria-hidden": "true", className: "size-4" }),
1692
+ /* @__PURE__ */ jsx("span", { children: msg.file.dropHere })
1693
+ ] }) : files.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 overflow-hidden", children: [
1694
+ visibleFiles.map((file) => {
1695
+ const isUploading2 = uploadingFiles.has(file.id);
1696
+ if (isUploading2) {
1697
+ return /* @__PURE__ */ jsx(
1698
+ Skeleton,
1699
+ {
1700
+ className: "h-5 shrink-0 px-1.5",
1701
+ style: {
1702
+ width: `${Math.min(file.name.length * 8 + 30, 100)}px`
1703
+ }
1704
+ },
1705
+ file.id
1706
+ );
1707
+ }
1708
+ const FileIcon = getFileIcon(file.type);
1709
+ return /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "gap-1 px-1.5 py-px", children: [
1710
+ FileIcon && /* @__PURE__ */ jsx(FileIcon, { className: "size-3 shrink-0" }),
1711
+ /* @__PURE__ */ jsx("span", { className: "max-w-[100px] truncate", children: file.name })
1712
+ ] }, file.id);
1713
+ }),
1714
+ hiddenFileCount > 0 && /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: "px-1.5 py-px text-muted-foreground", children: [
1715
+ "+",
1716
+ hiddenFileCount
1717
+ ] })
1718
+ ] }) : null
1719
+ ]
1720
+ }
1721
+ );
1722
+ }
1723
+ export {
1724
+ CheckboxCell,
1725
+ DateCell,
1726
+ FileCell,
1727
+ LongTextCell,
1728
+ MultiSelectCell,
1729
+ NumberCell,
1730
+ SelectCell,
1731
+ ShortTextCell,
1732
+ UrlCell
1733
+ };
1734
+ //# sourceMappingURL=data-grid-cell-variants.js.map