@xcelsior/ui-spreadsheets 1.1.13 → 1.1.14

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/dist/index.js CHANGED
@@ -187,7 +187,162 @@ function cn(...inputs) {
187
187
  }
188
188
 
189
189
  // src/components/SpreadsheetCell.tsx
190
+ var import_react4 = require("react");
191
+
192
+ // src/hooks/useSpreadsheetPinning.ts
190
193
  var import_react3 = require("react");
194
+ var ROW_INDEX_COLUMN_ID = "__row_index__";
195
+ var ROW_INDEX_COLUMN_WIDTH = 80;
196
+ var MIN_PINNED_COLUMN_WIDTH = 100;
197
+ function useSpreadsheetPinning({
198
+ columns,
199
+ columnGroups,
200
+ showRowIndex = true,
201
+ defaultPinnedColumns = [],
202
+ defaultPinnedRightColumns = []
203
+ }) {
204
+ const [pinnedColumns, setPinnedColumns] = (0, import_react3.useState)(() => {
205
+ const map = /* @__PURE__ */ new Map();
206
+ defaultPinnedColumns.forEach((col) => {
207
+ map.set(col, "left");
208
+ });
209
+ defaultPinnedRightColumns.forEach((col) => {
210
+ map.set(col, "right");
211
+ });
212
+ return map;
213
+ });
214
+ const [collapsedGroups, setCollapsedGroups] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
215
+ const isRowIndexPinned = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
216
+ const handleTogglePin = (0, import_react3.useCallback)((columnId) => {
217
+ setPinnedColumns((prev) => {
218
+ const newMap = new Map(prev);
219
+ if (newMap.has(columnId)) {
220
+ newMap.delete(columnId);
221
+ } else {
222
+ newMap.set(columnId, "left");
223
+ }
224
+ return newMap;
225
+ });
226
+ }, []);
227
+ const setPinnedColumnsFromIds = (0, import_react3.useCallback)((columnIds) => {
228
+ const map = /* @__PURE__ */ new Map();
229
+ columnIds.forEach((col) => {
230
+ map.set(col, "left");
231
+ });
232
+ setPinnedColumns(map);
233
+ }, []);
234
+ const handleToggleGroupCollapse = (0, import_react3.useCallback)((groupId) => {
235
+ setCollapsedGroups((prev) => {
236
+ const newSet = new Set(prev);
237
+ if (newSet.has(groupId)) {
238
+ newSet.delete(groupId);
239
+ } else {
240
+ newSet.add(groupId);
241
+ }
242
+ return newSet;
243
+ });
244
+ }, []);
245
+ const visibleColumns = (0, import_react3.useMemo)(() => {
246
+ if (!columns || !Array.isArray(columns) || columns.length === 0) {
247
+ return [];
248
+ }
249
+ let result = [...columns];
250
+ if (columnGroups && Array.isArray(columnGroups)) {
251
+ result = result.filter((column) => {
252
+ const group = columnGroups.find((g) => g.columns.includes(column.id));
253
+ if (!group) return true;
254
+ if (!collapsedGroups.has(group.id)) return true;
255
+ return pinnedColumns.has(column.id);
256
+ });
257
+ }
258
+ const nonRowIndexPinned = Array.from(pinnedColumns.keys()).filter(
259
+ (id) => id !== ROW_INDEX_COLUMN_ID
260
+ );
261
+ if (nonRowIndexPinned.length === 0) {
262
+ return result;
263
+ }
264
+ const leftPinned = [];
265
+ const unpinned = [];
266
+ const rightPinned = [];
267
+ const pinnedLeftIds = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
268
+ const pinnedRightIds = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "right" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
269
+ for (const column of result) {
270
+ const pinSide = pinnedColumns.get(column.id);
271
+ if (pinSide === "left") {
272
+ leftPinned.push(column);
273
+ } else if (pinSide === "right") {
274
+ rightPinned.push(column);
275
+ } else {
276
+ unpinned.push(column);
277
+ }
278
+ }
279
+ leftPinned.sort((a, b) => pinnedLeftIds.indexOf(a.id) - pinnedLeftIds.indexOf(b.id));
280
+ rightPinned.sort((a, b) => pinnedRightIds.indexOf(a.id) - pinnedRightIds.indexOf(b.id));
281
+ return [...leftPinned, ...unpinned, ...rightPinned];
282
+ }, [columns, columnGroups, collapsedGroups, pinnedColumns]);
283
+ const getColumnLeftOffset = (0, import_react3.useCallback)(
284
+ (columnId) => {
285
+ if (columnId === ROW_INDEX_COLUMN_ID) {
286
+ return 0;
287
+ }
288
+ const pinnedLeft = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
289
+ const index = pinnedLeft.indexOf(columnId);
290
+ const isRowIndexPinnedNow = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
291
+ const baseOffset = showRowIndex && isRowIndexPinnedNow ? ROW_INDEX_COLUMN_WIDTH : 0;
292
+ if (index === -1) return baseOffset;
293
+ let offset = baseOffset;
294
+ for (let i = 0; i < index; i++) {
295
+ const col = columns.find((c) => c.id === pinnedLeft[i]);
296
+ const configuredWidth = col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH;
297
+ offset += Math.max(configuredWidth, MIN_PINNED_COLUMN_WIDTH);
298
+ }
299
+ return offset;
300
+ },
301
+ [pinnedColumns, columns, showRowIndex]
302
+ );
303
+ const isColumnPinned = (0, import_react3.useCallback)(
304
+ (columnId) => {
305
+ return pinnedColumns.has(columnId);
306
+ },
307
+ [pinnedColumns]
308
+ );
309
+ const getColumnPinSide = (0, import_react3.useCallback)(
310
+ (columnId) => {
311
+ return pinnedColumns.get(columnId);
312
+ },
313
+ [pinnedColumns]
314
+ );
315
+ const getColumnRightOffset = (0, import_react3.useCallback)(
316
+ (columnId) => {
317
+ const pinnedRight = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "right").map(([id]) => id);
318
+ const index = pinnedRight.indexOf(columnId);
319
+ if (index === -1) return 0;
320
+ let offset = 0;
321
+ for (let i = pinnedRight.length - 1; i > index; i--) {
322
+ const col = columns.find((c) => c.id === pinnedRight[i]);
323
+ const configuredWidth = col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH;
324
+ offset += Math.max(configuredWidth, MIN_PINNED_COLUMN_WIDTH);
325
+ }
326
+ return offset;
327
+ },
328
+ [pinnedColumns, columns]
329
+ );
330
+ return {
331
+ pinnedColumns,
332
+ isRowIndexPinned,
333
+ collapsedGroups,
334
+ visibleColumns,
335
+ handleTogglePin,
336
+ handleToggleGroupCollapse,
337
+ setPinnedColumnsFromIds,
338
+ getColumnLeftOffset,
339
+ getColumnRightOffset,
340
+ isColumnPinned,
341
+ getColumnPinSide
342
+ };
343
+ }
344
+
345
+ // src/components/SpreadsheetCell.tsx
191
346
  var import_jsx_runtime = require("react/jsx-runtime");
192
347
  var cellPaddingCompact = "px-1 py-px";
193
348
  var cellPaddingNormal = "px-2 py-1";
@@ -224,13 +379,13 @@ var SpreadsheetCell = ({
224
379
  onViewComments,
225
380
  className
226
381
  }) => {
227
- const [localValue, setLocalValue] = (0, import_react3.useState)(value);
228
- const inputRef = (0, import_react3.useRef)(null);
229
- const selectRef = (0, import_react3.useRef)(null);
230
- (0, import_react3.useEffect)(() => {
382
+ const [localValue, setLocalValue] = (0, import_react4.useState)(value);
383
+ const inputRef = (0, import_react4.useRef)(null);
384
+ const selectRef = (0, import_react4.useRef)(null);
385
+ (0, import_react4.useEffect)(() => {
231
386
  setLocalValue(value);
232
387
  }, [value]);
233
- (0, import_react3.useEffect)(() => {
388
+ (0, import_react4.useEffect)(() => {
234
389
  if (isEditing) {
235
390
  if (column.type === "select") {
236
391
  selectRef.current?.focus();
@@ -403,6 +558,12 @@ var SpreadsheetCell = ({
403
558
  style: {
404
559
  backgroundColor: isInSelection ? "rgb(239 246 255)" : getBackgroundColor(),
405
560
  minWidth: column.minWidth || column.width,
561
+ // Pinned columns must have a fixed width so sticky offsets stay correct.
562
+ // Enforce MIN_PINNED_COLUMN_WIDTH so header actions always fit.
563
+ ...isPinned && {
564
+ width: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
565
+ maxWidth: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH)
566
+ },
406
567
  ...positionStyles,
407
568
  ...selectionBorderStyles
408
569
  },
@@ -474,7 +635,7 @@ var SpreadsheetCell = ({
474
635
  );
475
636
  };
476
637
  SpreadsheetCell.displayName = "SpreadsheetCell";
477
- var MemoizedSpreadsheetCell = (0, import_react3.memo)(SpreadsheetCell, (prevProps, nextProps) => {
638
+ var MemoizedSpreadsheetCell = (0, import_react4.memo)(SpreadsheetCell, (prevProps, nextProps) => {
478
639
  if (prevProps.isEditing !== nextProps.isEditing) return false;
479
640
  if (prevProps.isFocused !== nextProps.isFocused) return false;
480
641
  if (prevProps.value !== nextProps.value) return false;
@@ -503,7 +664,7 @@ var MemoizedSpreadsheetCell = (0, import_react3.memo)(SpreadsheetCell, (prevProp
503
664
  MemoizedSpreadsheetCell.displayName = "MemoizedSpreadsheetCell";
504
665
 
505
666
  // src/components/SpreadsheetFilterDropdown.tsx
506
- var import_react4 = require("react");
667
+ var import_react5 = require("react");
507
668
  var import_jsx_runtime2 = require("react/jsx-runtime");
508
669
  var TEXT_OPERATORS = [
509
670
  { value: "contains", label: "Contains" },
@@ -549,28 +710,28 @@ var SpreadsheetFilterDropdown = ({
549
710
  onClose,
550
711
  className
551
712
  }) => {
552
- const [textOperator, setTextOperator] = (0, import_react4.useState)(
713
+ const [textOperator, setTextOperator] = (0, import_react5.useState)(
553
714
  filter?.textCondition?.operator || "contains"
554
715
  );
555
- const [textValue, setTextValue] = (0, import_react4.useState)(filter?.textCondition?.value || "");
556
- const [numberOperator, setNumberOperator] = (0, import_react4.useState)(
716
+ const [textValue, setTextValue] = (0, import_react5.useState)(filter?.textCondition?.value || "");
717
+ const [numberOperator, setNumberOperator] = (0, import_react5.useState)(
557
718
  filter?.numberCondition?.operator || "equals"
558
719
  );
559
- const [numberValue, setNumberValue] = (0, import_react4.useState)(
720
+ const [numberValue, setNumberValue] = (0, import_react5.useState)(
560
721
  filter?.numberCondition?.value?.toString() || ""
561
722
  );
562
- const [numberValueTo, setNumberValueTo] = (0, import_react4.useState)(
723
+ const [numberValueTo, setNumberValueTo] = (0, import_react5.useState)(
563
724
  filter?.numberCondition?.valueTo?.toString() || ""
564
725
  );
565
- const [dateOperator, setDateOperator] = (0, import_react4.useState)(
726
+ const [dateOperator, setDateOperator] = (0, import_react5.useState)(
566
727
  filter?.dateCondition?.operator || "equals"
567
728
  );
568
- const [dateValue, setDateValue] = (0, import_react4.useState)(filter?.dateCondition?.value || "");
569
- const [dateValueTo, setDateValueTo] = (0, import_react4.useState)(filter?.dateCondition?.valueTo || "");
570
- const dropdownRef = (0, import_react4.useRef)(null);
729
+ const [dateValue, setDateValue] = (0, import_react5.useState)(filter?.dateCondition?.value || "");
730
+ const [dateValueTo, setDateValueTo] = (0, import_react5.useState)(filter?.dateCondition?.valueTo || "");
731
+ const dropdownRef = (0, import_react5.useRef)(null);
571
732
  const isNumeric = column.type === "number";
572
733
  const isDate = column.type === "date";
573
- (0, import_react4.useEffect)(() => {
734
+ (0, import_react5.useEffect)(() => {
574
735
  const handleClickOutside = (event) => {
575
736
  if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
576
737
  onClose();
@@ -808,7 +969,7 @@ var SpreadsheetFilterDropdown = ({
808
969
  SpreadsheetFilterDropdown.displayName = "SpreadsheetFilterDropdown";
809
970
 
810
971
  // src/components/SpreadsheetToolbar.tsx
811
- var import_react5 = __toESM(require("react"));
972
+ var import_react6 = __toESM(require("react"));
812
973
  var import_jsx_runtime3 = require("react/jsx-runtime");
813
974
  var SpreadsheetToolbar = ({
814
975
  zoom,
@@ -835,9 +996,9 @@ var SpreadsheetToolbar = ({
835
996
  onClearFilters,
836
997
  className
837
998
  }) => {
838
- const [showMoreMenu, setShowMoreMenu] = import_react5.default.useState(false);
839
- const menuRef = import_react5.default.useRef(null);
840
- import_react5.default.useEffect(() => {
999
+ const [showMoreMenu, setShowMoreMenu] = import_react6.default.useState(false);
1000
+ const menuRef = import_react6.default.useRef(null);
1001
+ import_react6.default.useEffect(() => {
841
1002
  const handleClickOutside = (event) => {
842
1003
  if (menuRef.current && !menuRef.current.contains(event.target)) {
843
1004
  setShowMoreMenu(false);
@@ -1244,6 +1405,12 @@ var SpreadsheetHeader = ({
1244
1405
  backgroundColor: highlightColor || "rgb(243 244 246)",
1245
1406
  // gray-100
1246
1407
  minWidth: column.minWidth || column.width,
1408
+ // Pinned columns must have a fixed width so sticky offsets stay correct.
1409
+ // Enforce MIN_PINNED_COLUMN_WIDTH so header actions (pin/filter/highlight) always fit.
1410
+ ...isPinned && {
1411
+ width: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
1412
+ maxWidth: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH)
1413
+ },
1247
1414
  top: 0,
1248
1415
  // For sticky header
1249
1416
  ...positionStyles
@@ -1276,156 +1443,6 @@ var SpreadsheetHeader = ({
1276
1443
  };
1277
1444
  SpreadsheetHeader.displayName = "SpreadsheetHeader";
1278
1445
 
1279
- // src/hooks/useSpreadsheetPinning.ts
1280
- var import_react6 = require("react");
1281
- var ROW_INDEX_COLUMN_ID = "__row_index__";
1282
- var ROW_INDEX_COLUMN_WIDTH = 80;
1283
- function useSpreadsheetPinning({
1284
- columns,
1285
- columnGroups,
1286
- showRowIndex = true,
1287
- defaultPinnedColumns = [],
1288
- defaultPinnedRightColumns = []
1289
- }) {
1290
- const [pinnedColumns, setPinnedColumns] = (0, import_react6.useState)(() => {
1291
- const map = /* @__PURE__ */ new Map();
1292
- defaultPinnedColumns.forEach((col) => {
1293
- map.set(col, "left");
1294
- });
1295
- defaultPinnedRightColumns.forEach((col) => {
1296
- map.set(col, "right");
1297
- });
1298
- return map;
1299
- });
1300
- const [collapsedGroups, setCollapsedGroups] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
1301
- const isRowIndexPinned = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
1302
- const handleTogglePin = (0, import_react6.useCallback)((columnId) => {
1303
- setPinnedColumns((prev) => {
1304
- const newMap = new Map(prev);
1305
- if (newMap.has(columnId)) {
1306
- newMap.delete(columnId);
1307
- } else {
1308
- newMap.set(columnId, "left");
1309
- }
1310
- return newMap;
1311
- });
1312
- }, []);
1313
- const setPinnedColumnsFromIds = (0, import_react6.useCallback)((columnIds) => {
1314
- const map = /* @__PURE__ */ new Map();
1315
- columnIds.forEach((col) => {
1316
- map.set(col, "left");
1317
- });
1318
- setPinnedColumns(map);
1319
- }, []);
1320
- const handleToggleGroupCollapse = (0, import_react6.useCallback)((groupId) => {
1321
- setCollapsedGroups((prev) => {
1322
- const newSet = new Set(prev);
1323
- if (newSet.has(groupId)) {
1324
- newSet.delete(groupId);
1325
- } else {
1326
- newSet.add(groupId);
1327
- }
1328
- return newSet;
1329
- });
1330
- }, []);
1331
- const visibleColumns = (0, import_react6.useMemo)(() => {
1332
- if (!columns || !Array.isArray(columns) || columns.length === 0) {
1333
- return [];
1334
- }
1335
- let result = [...columns];
1336
- if (columnGroups && Array.isArray(columnGroups)) {
1337
- result = result.filter((column) => {
1338
- const group = columnGroups.find((g) => g.columns.includes(column.id));
1339
- if (!group) return true;
1340
- if (!collapsedGroups.has(group.id)) return true;
1341
- return pinnedColumns.has(column.id);
1342
- });
1343
- }
1344
- const nonRowIndexPinned = Array.from(pinnedColumns.keys()).filter(
1345
- (id) => id !== ROW_INDEX_COLUMN_ID
1346
- );
1347
- if (nonRowIndexPinned.length === 0) {
1348
- return result;
1349
- }
1350
- const leftPinned = [];
1351
- const unpinned = [];
1352
- const rightPinned = [];
1353
- const pinnedLeftIds = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
1354
- const pinnedRightIds = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "right" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
1355
- for (const column of result) {
1356
- const pinSide = pinnedColumns.get(column.id);
1357
- if (pinSide === "left") {
1358
- leftPinned.push(column);
1359
- } else if (pinSide === "right") {
1360
- rightPinned.push(column);
1361
- } else {
1362
- unpinned.push(column);
1363
- }
1364
- }
1365
- leftPinned.sort((a, b) => pinnedLeftIds.indexOf(a.id) - pinnedLeftIds.indexOf(b.id));
1366
- rightPinned.sort((a, b) => pinnedRightIds.indexOf(a.id) - pinnedRightIds.indexOf(b.id));
1367
- return [...leftPinned, ...unpinned, ...rightPinned];
1368
- }, [columns, columnGroups, collapsedGroups, pinnedColumns]);
1369
- const getColumnLeftOffset = (0, import_react6.useCallback)(
1370
- (columnId) => {
1371
- if (columnId === ROW_INDEX_COLUMN_ID) {
1372
- return 0;
1373
- }
1374
- const pinnedLeft = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
1375
- const index = pinnedLeft.indexOf(columnId);
1376
- const isRowIndexPinnedNow = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
1377
- const baseOffset = showRowIndex && isRowIndexPinnedNow ? ROW_INDEX_COLUMN_WIDTH : 0;
1378
- if (index === -1) return baseOffset;
1379
- let offset = baseOffset;
1380
- for (let i = 0; i < index; i++) {
1381
- const col = columns.find((c) => c.id === pinnedLeft[i]);
1382
- offset += col?.minWidth || col?.width || 100;
1383
- }
1384
- return offset;
1385
- },
1386
- [pinnedColumns, columns, showRowIndex]
1387
- );
1388
- const isColumnPinned = (0, import_react6.useCallback)(
1389
- (columnId) => {
1390
- return pinnedColumns.has(columnId);
1391
- },
1392
- [pinnedColumns]
1393
- );
1394
- const getColumnPinSide = (0, import_react6.useCallback)(
1395
- (columnId) => {
1396
- return pinnedColumns.get(columnId);
1397
- },
1398
- [pinnedColumns]
1399
- );
1400
- const getColumnRightOffset = (0, import_react6.useCallback)(
1401
- (columnId) => {
1402
- const pinnedRight = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "right").map(([id]) => id);
1403
- const index = pinnedRight.indexOf(columnId);
1404
- if (index === -1) return 0;
1405
- let offset = 0;
1406
- for (let i = pinnedRight.length - 1; i > index; i--) {
1407
- const col = columns.find((c) => c.id === pinnedRight[i]);
1408
- offset += col?.minWidth || col?.width || 100;
1409
- }
1410
- return offset;
1411
- },
1412
- [pinnedColumns, columns]
1413
- );
1414
- return {
1415
- pinnedColumns,
1416
- isRowIndexPinned,
1417
- collapsedGroups,
1418
- visibleColumns,
1419
- handleTogglePin,
1420
- handleToggleGroupCollapse,
1421
- setPinnedColumnsFromIds,
1422
- getColumnLeftOffset,
1423
- getColumnRightOffset,
1424
- isColumnPinned,
1425
- getColumnPinSide
1426
- };
1427
- }
1428
-
1429
1446
  // src/components/RowIndexColumnHeader.tsx
1430
1447
  var import_jsx_runtime6 = require("react/jsx-runtime");
1431
1448
  var cellPaddingCompact3 = "px-1 py-0.5";
@@ -4052,7 +4069,9 @@ function Spreadsheet({
4052
4069
  position: "sticky",
4053
4070
  left: isPinnedLeft ? `${getColumnLeftOffset(item.columnId)}px` : void 0,
4054
4071
  right: !isPinnedLeft ? `${getColumnRightOffset(item.columnId)}px` : void 0,
4055
- minWidth: col?.minWidth || col?.width
4072
+ minWidth: Math.max(col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
4073
+ width: Math.max(col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
4074
+ maxWidth: Math.max(col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH)
4056
4075
  }
4057
4076
  },
4058
4077
  `pinned-group-${item.columnId}`