@comitor/ui 1.10.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 (101) 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/package.json +33 -15
@@ -0,0 +1,189 @@
1
+ import { RowData, Cell, TableMeta } from '@tanstack/react-table';
2
+
3
+ /** Hướng đọc của lưới (mặc định "ltr" — tiếng Việt) */
4
+ type Direction = "ltr" | "rtl";
5
+ /** Chiều cao dòng: short 36px, medium 56px, tall 76px, extra-tall 96px */
6
+ type RowHeightValue = "short" | "medium" | "tall" | "extra-tall";
7
+ interface CellSelectOption {
8
+ label: string;
9
+ value: string;
10
+ icon?: React.ComponentType<React.ComponentProps<"svg">>;
11
+ /** Số lượng hiển thị bên phải trong danh sách lọc */
12
+ count?: number;
13
+ }
14
+ /**
15
+ * Kiểu ô của một cột — khai báo trong `columnDef.meta.cell`.
16
+ * Quyết định cả cách hiển thị, cách sửa, cách dán và bộ toán tử lọc.
17
+ */
18
+ type CellOpts = {
19
+ variant: "short-text";
20
+ } | {
21
+ variant: "long-text";
22
+ } | {
23
+ variant: "number";
24
+ min?: number;
25
+ max?: number;
26
+ step?: number;
27
+ } | {
28
+ variant: "select";
29
+ options: CellSelectOption[];
30
+ } | {
31
+ variant: "multi-select";
32
+ options: CellSelectOption[];
33
+ } | {
34
+ variant: "checkbox";
35
+ } | {
36
+ /** Giá trị lưu dạng chuỗi "yyyy-MM-dd", hiển thị dd/MM/yyyy */
37
+ variant: "date";
38
+ } | {
39
+ variant: "url";
40
+ } | {
41
+ variant: "file";
42
+ /** Giới hạn dung lượng mỗi tệp (byte) — mặc định 10MB */
43
+ maxFileSize?: number;
44
+ /** Số tệp tối đa mỗi ô — mặc định 10 */
45
+ maxFiles?: number;
46
+ /** Chuỗi `accept` của input file, vd "image/*,.pdf" */
47
+ accept?: string;
48
+ multiple?: boolean;
49
+ };
50
+ /** Một thay đổi ô: dòng (theo thứ tự đang hiển thị) + cột + giá trị mới */
51
+ interface CellUpdate {
52
+ rowIndex: number;
53
+ columnId: string;
54
+ value: unknown;
55
+ }
56
+ declare module "@tanstack/react-table" {
57
+ interface ColumnMeta<TData extends RowData, TValue> {
58
+ /** Tên cột hiển thị trong menu Lọc/Sắp xếp/Hiển thị cột */
59
+ label?: string;
60
+ /** Kiểu ô của cột — xem {@link CellOpts} */
61
+ cell?: CellOpts;
62
+ }
63
+ interface TableMeta<TData extends RowData> {
64
+ dataGridRef?: React.RefObject<HTMLElement | null>;
65
+ cellMapRef?: React.RefObject<Map<string, HTMLDivElement>>;
66
+ focusedCell?: CellPosition | null;
67
+ editingCell?: CellPosition | null;
68
+ selectionState?: SelectionState;
69
+ searchOpen?: boolean;
70
+ getIsCellSelected?: (rowIndex: number, columnId: string) => boolean;
71
+ getIsSearchMatch?: (rowIndex: number, columnId: string) => boolean;
72
+ getIsActiveSearchMatch?: (rowIndex: number, columnId: string) => boolean;
73
+ getVisualRowIndex?: (rowId: string) => number | undefined;
74
+ scrollToCell?: (rowIndex: number, columnId: string, align?: "auto" | "start" | "center" | "end") => void;
75
+ rowHeight?: RowHeightValue;
76
+ onRowHeightChange?: (value: RowHeightValue) => void;
77
+ onRowSelect?: (rowId: string, checked: boolean, shiftKey: boolean) => void;
78
+ onDataUpdate?: (params: CellUpdate | Array<CellUpdate>) => void;
79
+ onRowsDelete?: (rowIndices: number[]) => void | Promise<void>;
80
+ onColumnClick?: (columnId: string) => void;
81
+ onCellClick?: (rowIndex: number, columnId: string, event?: React.MouseEvent) => void;
82
+ onCellDoubleClick?: (rowIndex: number, columnId: string) => void;
83
+ onCellMouseDown?: (rowIndex: number, columnId: string, event: React.MouseEvent) => void;
84
+ onCellMouseEnter?: (rowIndex: number, columnId: string) => void;
85
+ onCellMouseUp?: () => void;
86
+ onCellContextMenu?: (rowIndex: number, columnId: string, event: React.MouseEvent) => void;
87
+ onCellEditingStart?: (rowIndex: number, columnId: string) => void;
88
+ onCellEditingStop?: (opts?: {
89
+ direction?: NavigationDirection;
90
+ moveToNextRow?: boolean;
91
+ }) => void;
92
+ onCellsCopy?: () => void;
93
+ onCellsCut?: () => void;
94
+ onCellsPaste?: (expand?: boolean) => void;
95
+ onSelectionClear?: () => void;
96
+ onFilesUpload?: (params: {
97
+ files: File[];
98
+ rowIndex: number;
99
+ columnId: string;
100
+ }) => Promise<FileCellData[]>;
101
+ onFilesDelete?: (params: {
102
+ fileIds: string[];
103
+ rowIndex: number;
104
+ columnId: string;
105
+ }) => void | Promise<void>;
106
+ contextMenu?: ContextMenuState;
107
+ onContextMenuOpenChange?: (open: boolean) => void;
108
+ pasteDialog?: PasteDialogState;
109
+ onPasteDialogOpenChange?: (open: boolean) => void;
110
+ readOnly?: boolean;
111
+ }
112
+ }
113
+ /** Vị trí một ô: chỉ số dòng đang hiển thị + id cột */
114
+ interface CellPosition {
115
+ rowIndex: number;
116
+ columnId: string;
117
+ }
118
+ interface CellRange {
119
+ start: CellPosition;
120
+ end: CellPosition;
121
+ }
122
+ interface SelectionState {
123
+ /** Khoá ô đang chọn dạng `"{rowIndex}:{columnId}"` */
124
+ selectedCells: Set<string>;
125
+ selectionRange: CellRange | null;
126
+ /** Đang giữ chuột kéo chọn */
127
+ isSelecting: boolean;
128
+ }
129
+ interface ContextMenuState {
130
+ open: boolean;
131
+ x: number;
132
+ y: number;
133
+ }
134
+ interface PasteDialogState {
135
+ open: boolean;
136
+ /** Số dòng còn thiếu để dán hết nội dung clipboard */
137
+ rowsNeeded: number;
138
+ clipboardText: string;
139
+ }
140
+ type NavigationDirection = "up" | "down" | "left" | "right" | "home" | "end" | "ctrl+up" | "ctrl+down" | "ctrl+home" | "ctrl+end" | "pageup" | "pagedown" | "pageleft" | "pageright";
141
+ /** Trạng thái + hành vi của ô tìm kiếm trong lưới (bật bằng `enableSearch`) */
142
+ interface SearchState {
143
+ searchMatches: CellPosition[];
144
+ matchIndex: number;
145
+ searchOpen: boolean;
146
+ onSearchOpenChange: (open: boolean) => void;
147
+ searchQuery: string;
148
+ onSearchQueryChange: (query: string) => void;
149
+ onSearch: (query: string) => void;
150
+ onNavigateToNextMatch: () => void;
151
+ onNavigateToPrevMatch: () => void;
152
+ }
153
+ interface DataGridCellProps<TData> {
154
+ cell: Cell<TData, unknown>;
155
+ tableMeta: TableMeta<TData>;
156
+ rowIndex: number;
157
+ columnId: string;
158
+ rowHeight: RowHeightValue;
159
+ isEditing: boolean;
160
+ isFocused: boolean;
161
+ isSelected: boolean;
162
+ isSearchMatch: boolean;
163
+ isActiveSearchMatch: boolean;
164
+ readOnly: boolean;
165
+ }
166
+ /** Tệp đính kèm trong ô `variant: "file"` */
167
+ interface FileCellData {
168
+ id: string;
169
+ name: string;
170
+ size: number;
171
+ type: string;
172
+ /** Thiếu url = đang tải lên */
173
+ url?: string;
174
+ }
175
+ type TextFilterOperator = "contains" | "notContains" | "equals" | "notEquals" | "startsWith" | "endsWith" | "isEmpty" | "isNotEmpty";
176
+ type NumberFilterOperator = "equals" | "notEquals" | "lessThan" | "lessThanOrEqual" | "greaterThan" | "greaterThanOrEqual" | "isBetween" | "isEmpty" | "isNotEmpty";
177
+ type DateFilterOperator = "equals" | "notEquals" | "before" | "after" | "onOrBefore" | "onOrAfter" | "isBetween" | "isEmpty" | "isNotEmpty";
178
+ type SelectFilterOperator = "is" | "isNot" | "isAnyOf" | "isNoneOf" | "isEmpty" | "isNotEmpty";
179
+ type BooleanFilterOperator = "isTrue" | "isFalse";
180
+ type FilterOperator = TextFilterOperator | NumberFilterOperator | DateFilterOperator | SelectFilterOperator | BooleanFilterOperator;
181
+ /** Giá trị của một bộ lọc cột (đặt trong `columnFilters[].value`) */
182
+ interface FilterValue {
183
+ operator: FilterOperator;
184
+ value?: string | number | string[];
185
+ /** Vế thứ hai của toán tử `isBetween` */
186
+ endValue?: string | number;
187
+ }
188
+
189
+ export type { BooleanFilterOperator, CellOpts, CellPosition, CellRange, CellSelectOption, CellUpdate, ContextMenuState, DataGridCellProps, DateFilterOperator, Direction, FileCellData, FilterOperator, FilterValue, NavigationDirection, NumberFilterOperator, PasteDialogState, RowHeightValue, SearchState, SelectFilterOperator, SelectionState, TextFilterOperator };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ declare function useAsRef<T>(props: T): React.RefObject<T>;
4
+
5
+ export { useAsRef };
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
3
+ function useAsRef(props) {
4
+ const ref = React.useRef(props);
5
+ useIsomorphicLayoutEffect(() => {
6
+ ref.current = props;
7
+ });
8
+ return ref;
9
+ }
10
+ export {
11
+ useAsRef
12
+ };
13
+ //# sourceMappingURL=use-as-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data-grid/use-as-ref.ts"],"sourcesContent":["import * as React from \"react\";\n\nimport { useIsomorphicLayoutEffect } from \"./use-isomorphic-layout-effect.js\";\n\nfunction useAsRef<T>(props: T) {\n const ref = React.useRef<T>(props);\n\n useIsomorphicLayoutEffect(() => {\n ref.current = props;\n });\n\n return ref;\n}\n\nexport { useAsRef };\n"],"mappings":"AAAA,YAAY,WAAW;AAEvB,SAAS,iCAAiC;AAE1C,SAAS,SAAY,OAAU;AAC7B,QAAM,MAAM,MAAM,OAAU,KAAK;AAEjC,4BAA0B,MAAM;AAC9B,QAAI,UAAU;AAAA,EAChB,CAAC;AAED,SAAO;AACT;","names":[]}
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+
3
+ interface UseBadgeOverflowProps<T> {
4
+ items: T[];
5
+ getLabel: (item: T) => string;
6
+ containerRef: React.RefObject<HTMLElement | null>;
7
+ lineCount: number;
8
+ cacheKeyPrefix?: string;
9
+ iconSize?: number;
10
+ maxWidth?: number;
11
+ className?: string;
12
+ containerPadding?: number;
13
+ badgeGap?: number;
14
+ overflowBadgeWidth?: number;
15
+ }
16
+ interface UseBadgeOverflowReturn<T> {
17
+ visibleItems: T[];
18
+ hiddenCount: number;
19
+ containerWidth: number;
20
+ }
21
+ declare function useBadgeOverflow<T>({ items, getLabel, containerRef, lineCount, cacheKeyPrefix, containerPadding, badgeGap, overflowBadgeWidth, iconSize, maxWidth, className }: UseBadgeOverflowProps<T>): UseBadgeOverflowReturn<T>;
22
+ declare function clearBadgeWidthCache(): void;
23
+
24
+ export { clearBadgeWidthCache, useBadgeOverflow };
@@ -0,0 +1,123 @@
1
+ import * as React from "react";
2
+ const badgeWidthCache = /* @__PURE__ */ new Map();
3
+ const DEFAULT_CONTAINER_PADDING = 16;
4
+ const DEFAULT_BADGE_GAP = 4;
5
+ const DEFAULT_OVERFLOW_BADGE_WIDTH = 40;
6
+ function measureBadgeWidth({ label, cacheKey, iconSize, maxWidth, className }) {
7
+ const cached = badgeWidthCache.get(cacheKey);
8
+ if (cached !== void 0) {
9
+ return cached;
10
+ }
11
+ const measureEl = document.createElement("div");
12
+ measureEl.className = `inline-flex items-center rounded-md border px-1.5 text-xs font-semibold h-5 gap-1 shrink-0 absolute invisible pointer-events-none ${className ?? ""}`;
13
+ measureEl.style.whiteSpace = "nowrap";
14
+ if (iconSize) {
15
+ const icon = document.createElement("span");
16
+ icon.className = "shrink-0";
17
+ icon.style.width = `${iconSize}px`;
18
+ icon.style.height = `${iconSize}px`;
19
+ measureEl.appendChild(icon);
20
+ }
21
+ if (maxWidth) {
22
+ const text = document.createElement("span");
23
+ text.className = "truncate";
24
+ text.style.maxWidth = `${maxWidth}px`;
25
+ text.textContent = label;
26
+ measureEl.appendChild(text);
27
+ } else {
28
+ measureEl.textContent = label;
29
+ }
30
+ document.body.appendChild(measureEl);
31
+ const width = measureEl.offsetWidth;
32
+ document.body.removeChild(measureEl);
33
+ badgeWidthCache.set(cacheKey, width);
34
+ return width;
35
+ }
36
+ function useBadgeOverflow({
37
+ items,
38
+ getLabel,
39
+ containerRef,
40
+ lineCount,
41
+ cacheKeyPrefix = "",
42
+ containerPadding = DEFAULT_CONTAINER_PADDING,
43
+ badgeGap = DEFAULT_BADGE_GAP,
44
+ overflowBadgeWidth = DEFAULT_OVERFLOW_BADGE_WIDTH,
45
+ iconSize,
46
+ maxWidth,
47
+ className
48
+ }) {
49
+ const [containerWidth, setContainerWidth] = React.useState(0);
50
+ React.useEffect(() => {
51
+ if (!containerRef.current) return;
52
+ function measureWidth() {
53
+ if (containerRef.current) {
54
+ const width = containerRef.current.clientWidth - containerPadding;
55
+ setContainerWidth(width);
56
+ }
57
+ }
58
+ measureWidth();
59
+ const resizeObserver = new ResizeObserver(measureWidth);
60
+ resizeObserver.observe(containerRef.current);
61
+ return () => {
62
+ resizeObserver.disconnect();
63
+ };
64
+ }, [containerRef, containerPadding]);
65
+ const result = React.useMemo(() => {
66
+ if (!containerWidth || items.length === 0) {
67
+ return { visibleItems: items, hiddenCount: 0, containerWidth };
68
+ }
69
+ let currentLineWidth = 0;
70
+ let currentLine = 1;
71
+ const visible = [];
72
+ for (const item of items) {
73
+ const label = getLabel(item);
74
+ const cacheKey = cacheKeyPrefix ? `${cacheKeyPrefix}:${label}` : label;
75
+ const badgeWidth = measureBadgeWidth({
76
+ label,
77
+ cacheKey,
78
+ iconSize,
79
+ maxWidth,
80
+ className
81
+ });
82
+ const widthWithGap = badgeWidth + badgeGap;
83
+ if (currentLineWidth + widthWithGap <= containerWidth) {
84
+ currentLineWidth += widthWithGap;
85
+ visible.push(item);
86
+ } else if (currentLine < lineCount) {
87
+ currentLine++;
88
+ currentLineWidth = widthWithGap;
89
+ visible.push(item);
90
+ } else {
91
+ if (currentLineWidth + overflowBadgeWidth > containerWidth && visible.length > 0) {
92
+ visible.pop();
93
+ }
94
+ break;
95
+ }
96
+ }
97
+ return {
98
+ visibleItems: visible,
99
+ hiddenCount: Math.max(0, items.length - visible.length),
100
+ containerWidth
101
+ };
102
+ }, [
103
+ items,
104
+ getLabel,
105
+ containerWidth,
106
+ lineCount,
107
+ cacheKeyPrefix,
108
+ iconSize,
109
+ maxWidth,
110
+ className,
111
+ badgeGap,
112
+ overflowBadgeWidth
113
+ ]);
114
+ return result;
115
+ }
116
+ function clearBadgeWidthCache() {
117
+ badgeWidthCache.clear();
118
+ }
119
+ export {
120
+ clearBadgeWidthCache,
121
+ useBadgeOverflow
122
+ };
123
+ //# sourceMappingURL=use-badge-overflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data-grid/use-badge-overflow.ts"],"sourcesContent":["import * as React from \"react\";\n\nconst badgeWidthCache = new Map<string, number>();\n\nconst DEFAULT_CONTAINER_PADDING = 16; // px-2 = 8px * 2\nconst DEFAULT_BADGE_GAP = 4; // gap-1 = 4px\nconst DEFAULT_OVERFLOW_BADGE_WIDTH = 40; // Approximate width of \"+N\" badge\n\ninterface MeasureBadgeWidthProps {\n label: string;\n cacheKey: string;\n iconSize?: number;\n maxWidth?: number;\n className?: string;\n}\n\nfunction measureBadgeWidth({ label, cacheKey, iconSize, maxWidth, className }: MeasureBadgeWidthProps): number {\n const cached = badgeWidthCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const measureEl = document.createElement(\"div\");\n measureEl.className = `inline-flex items-center rounded-md border px-1.5 text-xs font-semibold h-5 gap-1 shrink-0 absolute invisible pointer-events-none ${\n className ?? \"\"\n }`;\n measureEl.style.whiteSpace = \"nowrap\";\n\n if (iconSize) {\n const icon = document.createElement(\"span\");\n icon.className = \"shrink-0\";\n icon.style.width = `${iconSize}px`;\n icon.style.height = `${iconSize}px`;\n measureEl.appendChild(icon);\n }\n\n if (maxWidth) {\n const text = document.createElement(\"span\");\n text.className = \"truncate\";\n text.style.maxWidth = `${maxWidth}px`;\n text.textContent = label;\n measureEl.appendChild(text);\n } else {\n measureEl.textContent = label;\n }\n\n document.body.appendChild(measureEl);\n const width = measureEl.offsetWidth;\n document.body.removeChild(measureEl);\n\n badgeWidthCache.set(cacheKey, width);\n return width;\n}\n\ninterface UseBadgeOverflowProps<T> {\n items: T[];\n getLabel: (item: T) => string;\n containerRef: React.RefObject<HTMLElement | null>;\n lineCount: number;\n cacheKeyPrefix?: string;\n iconSize?: number;\n maxWidth?: number;\n className?: string;\n containerPadding?: number;\n badgeGap?: number;\n overflowBadgeWidth?: number;\n}\n\ninterface UseBadgeOverflowReturn<T> {\n visibleItems: T[];\n hiddenCount: number;\n containerWidth: number;\n}\n\nexport function useBadgeOverflow<T>({\n items,\n getLabel,\n containerRef,\n lineCount,\n cacheKeyPrefix = \"\",\n containerPadding = DEFAULT_CONTAINER_PADDING,\n badgeGap = DEFAULT_BADGE_GAP,\n overflowBadgeWidth = DEFAULT_OVERFLOW_BADGE_WIDTH,\n iconSize,\n maxWidth,\n className\n}: UseBadgeOverflowProps<T>): UseBadgeOverflowReturn<T> {\n const [containerWidth, setContainerWidth] = React.useState(0);\n\n React.useEffect(() => {\n if (!containerRef.current) return;\n\n function measureWidth() {\n if (containerRef.current) {\n const width = containerRef.current.clientWidth - containerPadding;\n setContainerWidth(width);\n }\n }\n\n measureWidth();\n\n const resizeObserver = new ResizeObserver(measureWidth);\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [containerRef, containerPadding]);\n\n const result = React.useMemo(() => {\n if (!containerWidth || items.length === 0) {\n return { visibleItems: items, hiddenCount: 0, containerWidth };\n }\n\n let currentLineWidth = 0;\n let currentLine = 1;\n const visible: T[] = [];\n\n for (const item of items) {\n const label = getLabel(item);\n const cacheKey = cacheKeyPrefix ? `${cacheKeyPrefix}:${label}` : label;\n const badgeWidth = measureBadgeWidth({\n label,\n cacheKey,\n iconSize,\n maxWidth,\n className\n });\n const widthWithGap = badgeWidth + badgeGap;\n\n if (currentLineWidth + widthWithGap <= containerWidth) {\n currentLineWidth += widthWithGap;\n visible.push(item);\n } else if (currentLine < lineCount) {\n currentLine++;\n currentLineWidth = widthWithGap;\n visible.push(item);\n } else {\n if (currentLineWidth + overflowBadgeWidth > containerWidth && visible.length > 0) {\n visible.pop();\n }\n\n break;\n }\n }\n\n return {\n visibleItems: visible,\n hiddenCount: Math.max(0, items.length - visible.length),\n containerWidth\n };\n }, [\n items,\n getLabel,\n containerWidth,\n lineCount,\n cacheKeyPrefix,\n iconSize,\n maxWidth,\n className,\n badgeGap,\n overflowBadgeWidth\n ]);\n\n return result;\n}\n\nexport function clearBadgeWidthCache(): void {\n badgeWidthCache.clear();\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx
3
+ */
4
+ /**
5
+ * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
6
+ * prop or avoid re-executing effects when passed as a dependency
7
+ */
8
+ declare function useCallbackRef<T extends (...args: never[]) => unknown>(callback: T | undefined): T;
9
+
10
+ export { useCallbackRef };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ function useCallbackRef(callback) {
3
+ const callbackRef = React.useRef(callback);
4
+ React.useEffect(() => {
5
+ callbackRef.current = callback;
6
+ });
7
+ return React.useMemo(() => ((...args) => callbackRef.current?.(...args)), []);
8
+ }
9
+ export {
10
+ useCallbackRef
11
+ };
12
+ //# sourceMappingURL=use-callback-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data-grid/use-callback-ref.ts"],"sourcesContent":["import * as React from \"react\";\n\n/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx\n */\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef<T extends (...args: never[]) => unknown>(callback: T | undefined): T {\n const callbackRef = React.useRef(callback);\n\n React.useEffect(() => {\n callbackRef.current = callback;\n });\n\n // https://github.com/facebook/react/issues/19240\n return React.useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport { useCallbackRef };\n"],"mappings":"AAAA,YAAY,WAAW;AAUvB,SAAS,eAAwD,UAA4B;AAC3F,QAAM,cAAc,MAAM,OAAO,QAAQ;AAEzC,QAAM,UAAU,MAAM;AACpB,gBAAY,UAAU;AAAA,EACxB,CAAC;AAGD,SAAO,MAAM,QAAQ,OAAO,IAAI,SAAS,YAAY,UAAU,GAAG,IAAI,IAAS,CAAC,CAAC;AACnF;","names":[]}
@@ -0,0 +1,33 @@
1
+ interface UndoRedoCellUpdate {
2
+ rowId: string;
3
+ columnId: string;
4
+ previousValue: unknown;
5
+ newValue: unknown;
6
+ }
7
+ /**
8
+ * Tuỳ chọn của {@link useDataGridUndoRedo}. Lịch sử nằm hoàn toàn ở phía trình duyệt:
9
+ * hook tính ra mảng dữ liệu trước/sau rồi trả về qua `onDataChange` — ứng dụng tự
10
+ * đồng bộ lên máy chủ (hoặc bỏ qua nếu chỉ làm việc với state cục bộ).
11
+ */
12
+ interface UseDataGridUndoRedoProps<TData> {
13
+ data: TData[];
14
+ onDataChange: (data: TData[]) => void;
15
+ /** Khoá định danh bản ghi — phải khớp với `getRowId` của lưới */
16
+ getRowId: (row: TData) => string;
17
+ /** Số thao tác giữ trong lịch sử (mặc định 100) */
18
+ maxHistory?: number;
19
+ enabled?: boolean;
20
+ }
21
+ interface UseDataGridUndoRedoReturn<TData> {
22
+ canUndo: boolean;
23
+ canRedo: boolean;
24
+ onUndo: () => void;
25
+ onRedo: () => void;
26
+ onClear: () => void;
27
+ trackCellsUpdate: (updates: UndoRedoCellUpdate[]) => void;
28
+ trackRowsAdd: (rows: TData[]) => void;
29
+ trackRowsDelete: (rows: TData[]) => void;
30
+ }
31
+ declare function useDataGridUndoRedo<TData>({ data, onDataChange, getRowId, maxHistory, enabled }: UseDataGridUndoRedoProps<TData>): UseDataGridUndoRedoReturn<TData>;
32
+
33
+ export { type UndoRedoCellUpdate, useDataGridUndoRedo };