@xcelsior/ui-spreadsheets 1.1.0 → 1.1.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcelsior/ui-spreadsheets",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -111,6 +111,7 @@ export function Spreadsheet<T extends Record<string, any>>({
111
111
  totalItems,
112
112
  currentPage: controlledCurrentPage,
113
113
  pageSize: controlledPageSize,
114
+ sortConfig: controlledSortConfig,
114
115
  onPageChange,
115
116
  filters: controlledFilters,
116
117
  }: SpreadsheetProps<T>) {
@@ -145,7 +146,7 @@ export function Spreadsheet<T extends Record<string, any>>({
145
146
  onSortChange,
146
147
  serverSide,
147
148
  controlledFilters,
148
- controlledSortConfig: spreadsheetSettings?.defaultSort,
149
+ controlledSortConfig: controlledSortConfig ?? spreadsheetSettings?.defaultSort,
149
150
  });
150
151
 
151
152
  // Highlighting hook
@@ -182,6 +183,7 @@ export function Spreadsheet<T extends Record<string, any>>({
182
183
  } = useSpreadsheetPinning({
183
184
  columns,
184
185
  columnGroups,
186
+ defaultPinnedColumns: initialSettings?.defaultPinnedColumns,
185
187
  });
186
188
 
187
189
  // Comments hook
@@ -270,6 +272,35 @@ export function Spreadsheet<T extends Record<string, any>>({
270
272
  }));
271
273
  }, [sortConfig]);
272
274
 
275
+ // Sync pinned columns to spreadsheetSettings when pinning changes
276
+ useEffect(() => {
277
+ const pinnedColumnIds = Array.from(pinnedColumns.keys());
278
+ setSpreadsheetSettings((prev) => {
279
+ // Only update if the arrays are different to avoid unnecessary re-renders
280
+ const prevIds = prev.defaultPinnedColumns;
281
+ if (
282
+ prevIds.length === pinnedColumnIds.length &&
283
+ prevIds.every((id, idx) => id === pinnedColumnIds[idx])
284
+ ) {
285
+ return prev;
286
+ }
287
+ return {
288
+ ...prev,
289
+ defaultPinnedColumns: pinnedColumnIds,
290
+ };
291
+ });
292
+ }, [pinnedColumns]);
293
+
294
+ // Notify parent when settings change (skip initial render)
295
+ const isInitialMount = useRef(true);
296
+ useEffect(() => {
297
+ if (isInitialMount.current) {
298
+ isInitialMount.current = false;
299
+ return;
300
+ }
301
+ onSettingsChange?.(spreadsheetSettings);
302
+ }, [spreadsheetSettings, onSettingsChange]);
303
+
273
304
  const applyUndo = useCallback(() => {
274
305
  const entry = popUndoEntry();
275
306
  if (!entry || !onCellsEdit) return;
@@ -231,7 +231,7 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
231
231
  onKeyDown={handleCellKeyDown}
232
232
  data-cell-id={`${rowId}-${column.id}`}
233
233
  className={cn(
234
- 'border border-gray-200 group cursor-pointer select-none',
234
+ 'border border-gray-200 group cursor-pointer transition-colors select-none',
235
235
  compactMode ? 'text-[10px]' : 'text-xs',
236
236
  cellPadding,
237
237
  column.align === 'right' && 'text-right',
@@ -10,7 +10,6 @@ export interface UseSpreadsheetPinningOptions<T> {
10
10
  columnGroups?: SpreadsheetColumnGroup[];
11
11
  showRowIndex?: boolean;
12
12
  defaultPinnedColumns?: string[];
13
- defaultPinRowIndex?: boolean;
14
13
  }
15
14
 
16
15
  export interface UseSpreadsheetPinningReturn<T> {
@@ -26,14 +25,11 @@ export interface UseSpreadsheetPinningReturn<T> {
26
25
 
27
26
  // Actions
28
27
  handleTogglePin: (columnId: string) => void;
29
- handleToggleRowIndexPin: () => void;
30
28
  handleToggleGroupCollapse: (groupId: string) => void;
31
29
  setPinnedColumnsFromIds: (columnIds: string[]) => void;
32
- setRowIndexPinned: (pinned: boolean) => void;
33
30
 
34
31
  // Offset calculations
35
32
  getColumnLeftOffset: (columnId: string) => number;
36
- getRowIndexLeftOffset: () => number;
37
33
 
38
34
  // Utility
39
35
  isColumnPinned: (columnId: string) => boolean;
@@ -45,26 +41,22 @@ export function useSpreadsheetPinning<T>({
45
41
  columnGroups,
46
42
  showRowIndex = true,
47
43
  defaultPinnedColumns = [],
48
- defaultPinRowIndex = false,
49
44
  }: UseSpreadsheetPinningOptions<T>): UseSpreadsheetPinningReturn<T> {
50
- // Initialize pinned columns from defaults (excluding row index which is handled separately)
45
+ // Initialize pinned columns from defaults (including row index)
51
46
  const [pinnedColumns, setPinnedColumns] = useState<Map<string, 'left' | 'right'>>(() => {
52
47
  const map = new Map<string, 'left' | 'right'>();
53
48
  defaultPinnedColumns.forEach((col) => {
54
- if (col !== ROW_INDEX_COLUMN_ID) {
55
- map.set(col, 'left');
56
- }
49
+ map.set(col, 'left');
57
50
  });
58
51
  return map;
59
52
  });
60
53
 
61
- // Check if row index should be pinned from either defaultPinRowIndex or defaultPinnedColumns
62
- const [isRowIndexPinned, setIsRowIndexPinned] = useState(
63
- defaultPinRowIndex || defaultPinnedColumns.includes(ROW_INDEX_COLUMN_ID)
64
- );
65
54
  const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set());
66
55
 
67
- // Toggle column pin
56
+ // Derive isRowIndexPinned from pinnedColumns for convenience
57
+ const isRowIndexPinned = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
58
+
59
+ // Toggle column pin (works for any column including row index)
68
60
  const handleTogglePin = useCallback((columnId: string) => {
69
61
  setPinnedColumns((prev) => {
70
62
  const newMap = new Map(prev);
@@ -77,27 +69,13 @@ export function useSpreadsheetPinning<T>({
77
69
  });
78
70
  }, []);
79
71
 
80
- // Toggle row index pin
81
- const handleToggleRowIndexPin = useCallback(() => {
82
- setIsRowIndexPinned((prev) => !prev);
83
- }, []);
84
-
85
72
  // Set pinned columns from an array of column IDs
86
73
  const setPinnedColumnsFromIds = useCallback((columnIds: string[]) => {
87
74
  const map = new Map<string, 'left' | 'right'>();
88
75
  columnIds.forEach((col) => {
89
- if (col !== ROW_INDEX_COLUMN_ID) {
90
- map.set(col, 'left');
91
- }
76
+ map.set(col, 'left');
92
77
  });
93
78
  setPinnedColumns(map);
94
- // Also update row index pinned state
95
- setIsRowIndexPinned(columnIds.includes(ROW_INDEX_COLUMN_ID));
96
- }, []);
97
-
98
- // Set row index pinned state directly
99
- const setRowIndexPinned = useCallback((pinned: boolean) => {
100
- setIsRowIndexPinned(pinned);
101
79
  }, []);
102
80
 
103
81
  // Toggle group collapse
@@ -132,8 +110,11 @@ export function useSpreadsheetPinning<T>({
132
110
  });
133
111
  }
134
112
 
135
- // If no columns are pinned, return result as-is to preserve original order
136
- if (pinnedColumns.size === 0) {
113
+ // If no columns are pinned (excluding row index), return result as-is to preserve original order
114
+ const nonRowIndexPinned = Array.from(pinnedColumns.keys()).filter(
115
+ (id) => id !== ROW_INDEX_COLUMN_ID
116
+ );
117
+ if (nonRowIndexPinned.length === 0) {
137
118
  return result;
138
119
  }
139
120
 
@@ -144,10 +125,10 @@ export function useSpreadsheetPinning<T>({
144
125
 
145
126
  // Maintain the order of pinned columns as they were added
146
127
  const pinnedLeftIds = Array.from(pinnedColumns.entries())
147
- .filter(([, side]) => side === 'left')
128
+ .filter(([id, side]) => side === 'left' && id !== ROW_INDEX_COLUMN_ID)
148
129
  .map(([id]) => id);
149
130
  const pinnedRightIds = Array.from(pinnedColumns.entries())
150
- .filter(([, side]) => side === 'right')
131
+ .filter(([id, side]) => side === 'right' && id !== ROW_INDEX_COLUMN_ID)
151
132
  .map(([id]) => id);
152
133
 
153
134
  for (const column of result) {
@@ -168,32 +149,35 @@ export function useSpreadsheetPinning<T>({
168
149
  return [...leftPinned, ...unpinned, ...rightPinned];
169
150
  }, [columns, columnGroups, collapsedGroups, pinnedColumns]);
170
151
 
171
- // Get left offset for row index column
172
- const getRowIndexLeftOffset = useCallback((): number => {
173
- return 0;
174
- }, []);
175
-
176
152
  // Calculate column offset for sticky positioning
177
153
  const getColumnLeftOffset = useCallback(
178
154
  (columnId: string): number => {
155
+ // Row index column is always at left: 0 when pinned
156
+ if (columnId === ROW_INDEX_COLUMN_ID) {
157
+ return 0;
158
+ }
159
+
160
+ // Get left-pinned columns (excluding row index)
179
161
  const pinnedLeft = Array.from(pinnedColumns.entries())
180
- .filter(([, side]) => side === 'left')
162
+ .filter(([id, side]) => side === 'left' && id !== ROW_INDEX_COLUMN_ID)
181
163
  .map(([id]) => id);
182
164
  const index = pinnedLeft.indexOf(columnId);
183
165
 
184
166
  // Base offset includes the row index column if shown and pinned
185
- const baseOffset = showRowIndex && isRowIndexPinned ? ROW_INDEX_COLUMN_WIDTH : 0;
167
+ const isRowIndexPinnedNow = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
168
+ const baseOffset = showRowIndex && isRowIndexPinnedNow ? ROW_INDEX_COLUMN_WIDTH : 0;
186
169
 
187
170
  if (index === -1) return baseOffset;
188
171
 
189
172
  let offset = baseOffset;
190
173
  for (let i = 0; i < index; i++) {
191
174
  const col = columns.find((c) => c.id === pinnedLeft[i]);
192
- offset += col?.width || col?.minWidth || 100;
175
+ // Use minWidth || width to match the rendered cell width
176
+ offset += col?.minWidth || col?.width || 100;
193
177
  }
194
178
  return offset;
195
179
  },
196
- [pinnedColumns, columns, showRowIndex, isRowIndexPinned]
180
+ [pinnedColumns, columns, showRowIndex]
197
181
  );
198
182
 
199
183
  // Check if column is pinned
@@ -218,12 +202,9 @@ export function useSpreadsheetPinning<T>({
218
202
  collapsedGroups,
219
203
  visibleColumns,
220
204
  handleTogglePin,
221
- handleToggleRowIndexPin,
222
205
  handleToggleGroupCollapse,
223
206
  setPinnedColumnsFromIds,
224
- setRowIndexPinned,
225
207
  getColumnLeftOffset,
226
- getRowIndexLeftOffset,
227
208
  isColumnPinned,
228
209
  getColumnPinSide,
229
210
  };