pixelize-design-library 2.4.2-beta.47 → 2.4.2-beta.49

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 (36) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/COMPONENT-GOTCHAS.md +41 -8
  3. package/DESIGN-GUIDE.md +1 -1
  4. package/dist/Components/Common/chipTriggerSize.d.ts +10 -0
  5. package/dist/Components/Common/chipTriggerSize.js +15 -0
  6. package/dist/Components/SearchSelect/SearchSelect.js +48 -7
  7. package/dist/Components/SearchSelect/SearchSelectProps.d.ts +5 -1
  8. package/dist/Components/Select/SelectNative.d.ts +2 -0
  9. package/dist/Components/Select/SelectPopup.js +97 -36
  10. package/dist/Components/Select/SelectProps.d.ts +4 -0
  11. package/dist/Components/SelectV2/SelectV2.d.ts +2 -9
  12. package/dist/Components/SelectV2/SelectV2.js +2 -10
  13. package/dist/Components/Table/Table.js +6 -7
  14. package/dist/Components/Table/TableProps.d.ts +9 -1
  15. package/dist/Components/Table/components/Pagination.js +8 -5
  16. package/dist/Components/Table/components/TableBody.d.ts +2 -1
  17. package/dist/Components/Table/components/TableBody.js +82 -15
  18. package/dist/Components/Table/components/TableHeader.d.ts +1 -1
  19. package/dist/Components/Table/components/TableHeader.js +8 -9
  20. package/dist/Components/Table/components/rowMetrics.d.ts +0 -1
  21. package/dist/Components/Table/components/rowMetrics.js +1 -2
  22. package/dist/Components/Table/hooks/useTable.d.ts +5 -1
  23. package/dist/Components/Table/hooks/useTable.js +43 -7
  24. package/dist/Theme/tokens/primitives/controlSizes.js +2 -2
  25. package/dist/esm/Components/Common/chipTriggerSize.js +11 -0
  26. package/dist/esm/Components/SearchSelect/SearchSelect.js +48 -7
  27. package/dist/esm/Components/Select/SelectPopup.js +97 -36
  28. package/dist/esm/Components/SelectV2/SelectV2.js +2 -9
  29. package/dist/esm/Components/Table/Table.js +6 -7
  30. package/dist/esm/Components/Table/components/Pagination.js +8 -5
  31. package/dist/esm/Components/Table/components/TableBody.js +81 -16
  32. package/dist/esm/Components/Table/components/TableHeader.js +8 -9
  33. package/dist/esm/Components/Table/components/rowMetrics.js +0 -1
  34. package/dist/esm/Components/Table/hooks/useTable.js +44 -8
  35. package/dist/esm/Theme/tokens/primitives/controlSizes.js +2 -2
  36. package/package.json +1 -1
@@ -1,14 +1,7 @@
1
1
  import React from "react";
2
- import { type ControlSize } from "../../Theme/tokens/primitives/controlSizes";
2
+ import { chipTriggerSizeStyles } from "../Common/chipTriggerSize";
3
3
  import type { SelectV2Props } from "./SelectV2Props";
4
- type ChipSize = "xs" | "sm";
5
- export declare const chipTriggerSizeStyles: (size: ControlSize, hasChips: boolean, chipSize?: ChipSize) => {
6
- minH: string;
7
- fontSize: string;
8
- px: string;
9
- h?: string;
10
- py?: string;
11
- };
4
+ export { chipTriggerSizeStyles };
12
5
  /** @deprecated Use `Select` with `native={false}`. See MERGE-NOTES-select-deprecation.md. */
13
6
  declare const SelectV2: React.ForwardRefExoticComponent<SelectV2Props & React.RefAttributes<HTMLDivElement>>;
14
7
  /** @deprecated Use `Select` with `native={false}`. */
@@ -7,16 +7,8 @@ exports.chipTriggerSizeStyles = void 0;
7
7
  const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const react_1 = require("react");
9
9
  const Select_1 = __importDefault(require("../Select/Select"));
10
- const controlSizes_1 = require("../../Theme/tokens/primitives/controlSizes");
11
- const CHIP_TAG_HEIGHT_PX = { xs: 20, sm: 24 };
12
- const controlHeightPx = (size) => parseFloat(controlSizes_1.CONTROL_SIZES[size].height) * 16;
13
- const chipRowPaddingY = (size, chipSize) => `${(controlHeightPx(size) - CHIP_TAG_HEIGHT_PX[chipSize]) / 2 / 16}rem`;
14
- const chipTriggerSizeStyles = (size, hasChips, chipSize = "sm") => {
15
- const spec = controlSizes_1.CONTROL_SIZES[size];
16
- const base = { minH: spec.height, fontSize: spec.fontSize, px: spec.paddingX };
17
- return hasChips ? { ...base, py: chipRowPaddingY(size, chipSize) } : { ...base, h: spec.height };
18
- };
19
- exports.chipTriggerSizeStyles = chipTriggerSizeStyles;
10
+ const chipTriggerSize_1 = require("../Common/chipTriggerSize");
11
+ Object.defineProperty(exports, "chipTriggerSizeStyles", { enumerable: true, get: function () { return chipTriggerSize_1.chipTriggerSizeStyles; } });
20
12
  /** @deprecated Use `Select` with `native={false}`. See MERGE-NOTES-select-deprecation.md. */
21
13
  const SelectV2 = (0, react_1.forwardRef)(function SelectV2(props, ref) {
22
14
  return (0, jsx_runtime_1.jsx)(Select_1.default, { ...props, native: false, ref: ref });
@@ -83,7 +83,7 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
83
83
  }
84
84
  return {};
85
85
  }, [tablePreferencesData]);
86
- const { tableData, isContent, isLink, headerRefs, columnWidths, handleSort, handleCheckbox, filteredData, startRow, selection, columnsSort, currentPage, pages, rowsPerPage, handlePageSizeChange, setCurrentPage, columnsList, handleColumnPreferences, isSelecting, selectAllRowsRef, } = (0, useTable_1.default)({
86
+ const { tableData, isContent, isLink, headerRefs, columnWidths, slotRef, slotWidth, handleSort, handleCheckbox, filteredData, searchedData, startRow, selection, columnsSort, currentPage, pages, rowsPerPage, handlePageSizeChange, setCurrentPage, columnsList, handleColumnPreferences, isSelecting, selectAllRowsRef, } = (0, useTable_1.default)({
87
87
  data,
88
88
  isPagination,
89
89
  selections,
@@ -96,6 +96,7 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
96
96
  isServerPagination,
97
97
  onNoOfRowsPerPageChange,
98
98
  defaultVisibleColumns,
99
+ columnsSearch,
99
100
  });
100
101
  // Density + grouping are managed here so the Table Settings tabs can change +
101
102
  // persist them (seeded from the prop / saved preferences when they load).
@@ -178,9 +179,7 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
178
179
  setGroupByState(group);
179
180
  handleColumnPreferences(cols, { density: densityState, groupBy: group !== null && group !== void 0 ? group : null });
180
181
  };
181
- const _filteredData = (0, react_1.useMemo)(() => {
182
- return (0, table_1.searchAndSortData)(filteredData, columnsSearch);
183
- }, [columnsSearch, filteredData]);
182
+ const _filteredData = filteredData;
184
183
  // --- Monday-style grouping. Active whenever the user has picked a group column
185
184
  // (in Settings > Group). When active, the numbered pager / infinite scroll are
186
185
  // hidden and the body renders colored, collapsible groups over the loaded rows.
@@ -285,7 +284,7 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
285
284
  : "Showing all records";
286
285
  const tablePaginationText = isServerPagination
287
286
  ? `${startRow + 1} - ${Math.min(startRow + rowsPerPage, totalRecords)} of ${totalRecords}`
288
- : `${startRow + 1} - ${Math.min(startRow + rowsPerPage, tableData.length)} of ${tableData.length}`;
287
+ : `${startRow + 1} - ${Math.min(startRow + rowsPerPage, searchedData.length)} of ${searchedData.length}`;
289
288
  const rowHeight = (0, rowMetrics_1.rowHeightFor)(densityState);
290
289
  const headerHeight = (0, rowMetrics_1.pxOf)(variantTokens.header.height);
291
290
  const actualRows = isGrouped ? groupedSource.length : _filteredData.length;
@@ -406,7 +405,7 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
406
405
  headerActions && ((0, jsx_runtime_1.jsx)(HeaderActions_1.default, { actions: headerActions, selections: selection }, "actions")),
407
406
  ]
408
407
  .filter(Boolean)
409
- .flatMap((tool, i) => (i === 0 ? [tool] : [(0, jsx_runtime_1.jsx)(Divider_1.default, {}, `divider-${i}`), tool])), (0, jsx_runtime_1.jsx)(ActiveFilters_1.default, { columns: columnsList, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch }), (0, jsx_runtime_1.jsxs)(react_2.Box, { ml: "auto", display: "flex", alignItems: "center", gap: 2, children: [groupLoadMoreActive && loadMorePosition === "top" && (groupLoadMoreCaption || canGroupLoadMore) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { display: "flex", alignItems: "center", gap: 2, flex: "0 0 auto", children: [groupLoadMoreCaption && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], whiteSpace: "nowrap", children: groupLoadMoreCaption })), canGroupLoadMore && ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && !isCompactHeader && ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(Pagination_1.default, { currentPage: currentPage, setCurrentPage: setCurrentPage, rowsPerPage: rowsPerPage, pages: pages, paginationText: tablePaginationText, handlePageSizeChange: handlePageSizeChange, dataLength: tableData.length, isServerPagination: isServerPagination, paginationSelectOptions: paginationSelectOptions }) })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && isCompactHeader && tableData.length > 0 && ((0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isPaginationPopoverOpen, onClose: () => setIsPaginationPopoverOpen(false), placement: "bottom-end", children: [(0, jsx_runtime_1.jsx)(react_2.PopoverTrigger, { children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { "aria-label": "Pagination", size: "sm", variant: "ghost", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.EllipsisVertical, { size: 18 }), color: theme.colors.text[500], _hover: {
408
+ .flatMap((tool, i) => (i === 0 ? [tool] : [(0, jsx_runtime_1.jsx)(Divider_1.default, {}, `divider-${i}`), tool])), (0, jsx_runtime_1.jsx)(ActiveFilters_1.default, { columns: columnsList, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch }), (0, jsx_runtime_1.jsxs)(react_2.Box, { ml: "auto", display: "flex", alignItems: "center", gap: 2, children: [groupLoadMoreActive && loadMorePosition === "top" && (groupLoadMoreCaption || canGroupLoadMore) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { display: "flex", alignItems: "center", gap: 2, flex: "0 0 auto", children: [groupLoadMoreCaption && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], whiteSpace: "nowrap", children: groupLoadMoreCaption })), canGroupLoadMore && ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && !isCompactHeader && ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(Pagination_1.default, { currentPage: currentPage, setCurrentPage: setCurrentPage, rowsPerPage: rowsPerPage, pages: pages, paginationText: tablePaginationText, handlePageSizeChange: handlePageSizeChange, dataLength: tableData.length, isServerPagination: isServerPagination, paginationSelectOptions: paginationSelectOptions }) })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && isCompactHeader && ((0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isPaginationPopoverOpen, onClose: () => setIsPaginationPopoverOpen(false), placement: "bottom-end", children: [(0, jsx_runtime_1.jsx)(react_2.PopoverTrigger, { children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { "aria-label": "Pagination", size: "sm", variant: "ghost", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.EllipsisVertical, { size: 18 }), color: theme.colors.text[500], _hover: {
410
409
  color: theme.colors.accentText,
411
410
  }, onClick: () => setIsPaginationPopoverOpen((open) => !open) }) }), (0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsx)(react_2.PopoverContent, { maxW: "22rem", p: 2, zIndex: 1600, bg: theme.colors.surfaceRaised, boxShadow: "lg", children: (0, jsx_runtime_1.jsx)(react_2.PopoverBody, { p: 0, children: (0, jsx_runtime_1.jsx)(Pagination_1.default, { currentPage: currentPage, setCurrentPage: setCurrentPage, rowsPerPage: rowsPerPage, pages: pages, paginationText: tablePaginationText, handlePageSizeChange: handlePageSizeChange, dataLength: tableData.length, isServerPagination: isServerPagination, paginationSelectOptions: paginationSelectOptions }) }) }) })] }))] })] }), (0, jsx_runtime_1.jsxs)(react_2.TableContainer, { ref: tableContainerRef, position: "relative", h: isTableLoading ? tableMaxH : undefined, maxH: !isTableLoading ? tableMaxH : undefined, pb: isTableLoading ? 0 : compactScrollbarPadding, overflowY: isCompactRows ? "hidden" : "auto", overflowX: "auto", sx: {
412
411
  ...(0, scrollShadow_1.scrollShadowStyles)(theme.colors.background[50]),
@@ -436,5 +435,5 @@ function Table({ data, columns, onSelection, isLoading, isCheckbox = false, free
436
435
  backgroundColor: "transparent",
437
436
  zIndex: 999,
438
437
  },
439
- }, children: [(0, jsx_runtime_1.jsx)(react_3.Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: (0, jsx_runtime_1.jsx)(TableHeader_1.default, { columns: columnsList, isCheckbox: isCheckbox, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleSort: handleSort, headerRefs: headerRefs, columnWidths: columnWidths, columnsSort: columnsSort, noBorders: noBorders, handleCheckbox: handleCheckbox, isLoading: isTableLoading, checked: headerChecked, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, setColumnsSearch: setColumnsSearch, columnsSearch: columnsSearch, isSelecting: isSelecting }) }), (0, jsx_runtime_1.jsx)(react_3.Tbody, { children: (0, jsx_runtime_1.jsx)(TableBody_1.default, { data: isGrouped ? groupedSource : _filteredData, groups: renderGroups, onAddItem: onAddItem, columns: columnsList, startRow: isGrouped ? 0 : startRow, scrollContainerRef: tableContainerRef, isCheckbox: isCheckbox, columnWidths: columnWidths, noBorders: noBorders, freezedBgColor: freezedBgColor !== null && freezedBgColor !== void 0 ? freezedBgColor : theme.colors.backgroundColor.secondary, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleCheckbox: handleCheckbox, selections: selection, isLoading: isTableLoading, loadingSkeletonRows: loadingSkeletonRows, onRowClick: onRowClick, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, density: densityState, stripe: stripe, emptyState: emptyState }) })] }), canInfinite && isLoadingMore && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: "sm", color: theme.colors.accentText }), (0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
438
+ }, children: [(0, jsx_runtime_1.jsx)(react_3.Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: (0, jsx_runtime_1.jsx)(TableHeader_1.default, { columns: columnsList, isCheckbox: isCheckbox, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleSort: handleSort, headerRefs: headerRefs, columnWidths: columnWidths, slotRef: slotRef, slotWidth: slotWidth, columnsSort: columnsSort, noBorders: noBorders, handleCheckbox: handleCheckbox, isLoading: isTableLoading, checked: headerChecked, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, setColumnsSearch: setColumnsSearch, columnsSearch: columnsSearch, isSelecting: isSelecting }) }), (0, jsx_runtime_1.jsx)(react_3.Tbody, { children: (0, jsx_runtime_1.jsx)(TableBody_1.default, { data: isGrouped ? groupedSource : _filteredData, groups: renderGroups, onAddItem: onAddItem, columns: columnsList, startRow: isGrouped ? 0 : startRow, rowsPerPage: rowsPerPage, scrollContainerRef: tableContainerRef, isCheckbox: isCheckbox, columnWidths: columnWidths, slotWidth: slotWidth, noBorders: noBorders, freezedBgColor: freezedBgColor !== null && freezedBgColor !== void 0 ? freezedBgColor : theme.colors.backgroundColor.secondary, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleCheckbox: handleCheckbox, selections: selection, isLoading: isTableLoading, loadingSkeletonRows: loadingSkeletonRows, onRowClick: onRowClick, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, density: densityState, stripe: stripe, emptyState: emptyState }) })] }), canInfinite && isLoadingMore && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: "sm", color: theme.colors.accentText }), (0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
440
439
  }
@@ -185,7 +185,7 @@ export type TableHeaderProps = {
185
185
  /** Legacy hex fill seed per value; used only when `statusPalette` lacks that value. */
186
186
  statusColors?: Record<string, string>;
187
187
  columnType?: TableColumnType;
188
- /** Horizontal alignment for both the header label and the body cells. Defaults "start". */
188
+ /** Horizontal alignment for both the header label and the body cells. Defaults "start", except a full-bleed status cell, which centres until this is set. */
189
189
  align?: TableColumnAlign;
190
190
  /** Custom cell renderer; a column with `node` never fires onRowClick. */
191
191
  node?: (data: DataObject) => JSX.Element | string | number | undefined;
@@ -214,6 +214,9 @@ export type TableHeaderPageProps = Pick<TableProps, "columns" | "isCheckbox" | "
214
214
  handleHeaderClick?: (header: string, search: boolean) => void;
215
215
  headerRefs: React.MutableRefObject<(HTMLTableCellElement | null)[]>;
216
216
  columnWidths: number[];
217
+ /** Measures the select-all cell so frozen columns can butt against its real width. */
218
+ slotRef: React.Ref<HTMLTableCellElement>;
219
+ slotWidth: number;
217
220
  columnsSort: ColumnSortingProps[];
218
221
  handleCheckbox: (selection: string | number) => void;
219
222
  checked: boolean | "indeterminate";
@@ -226,7 +229,11 @@ export type TableHeaderPageProps = Pick<TableProps, "columns" | "isCheckbox" | "
226
229
  /** Props for the TableBody subcomponent; renders flat rows or grouped Monday-style board rows. */
227
230
  export type TableBodyPageProps = Pick<TableProps, "columns" | "isCheckbox" | "data" | "freezedBgColor" | "freezedTextColor" | "noBorders" | "isLoading" | "loadingSkeletonRows" | "onRowClick" | "isActionFreeze" | "density" | "stripe" | "emptyState"> & {
228
231
  startRow: number;
232
+ /** Page size; a change swaps the rendered rows without moving startRow, so scroll resets. */
233
+ rowsPerPage: number;
229
234
  columnWidths: number[];
235
+ /** Rendered width of the select-all cell; frozen columns start after it. */
236
+ slotWidth: number;
230
237
  handleCheckbox: (selection: string | number) => void;
231
238
  selections: (string | number)[];
232
239
  isContent: boolean;
@@ -262,6 +269,7 @@ export type ColumnHeaderProps = Pick<TableProps, "isCheckbox" | "noBorders"> & P
262
269
  header: TableHeaderProps;
263
270
  index: number;
264
271
  columnWidths: number[];
272
+ slotWidth: number;
265
273
  headerRefs: MutableRefObject<(HTMLTableCellElement | null)[]>;
266
274
  columnsSearch: Record<string, string>;
267
275
  setColumnsSearch: Dispatch<SetStateAction<Record<string, string>>>;
@@ -45,6 +45,8 @@ const useTable_1 = require("../hooks/useTable");
45
45
  const useCustomTheme_1 = require("../../../Theme/useCustomTheme");
46
46
  const ToolTip_1 = __importDefault(require("../../ToolTip/ToolTip"));
47
47
  const lucide_react_1 = require("lucide-react");
48
+ const STEP_SIZE = "1.5rem";
49
+ const STEP_ICON = 16;
48
50
  const Pagination = react_1.default.memo(({ paginationText, handlePageSizeChange, rowsPerPage, currentPage, pages, setCurrentPage, dataLength, isServerPagination, paginationSelectOptions, }) => {
49
51
  const computedOptions = (0, react_1.useMemo)(() => {
50
52
  if (paginationSelectOptions && paginationSelectOptions.length) {
@@ -56,10 +58,11 @@ const Pagination = react_1.default.memo(({ paginationText, handlePageSizeChange,
56
58
  return [...sizes].sort((a, b) => a - b);
57
59
  }, [paginationSelectOptions, dataLength, rowsPerPage]);
58
60
  const theme = (0, useCustomTheme_1.useCustomTheme)();
59
- // const isLeftDisabled = currentPage === 0;
60
- // const isRightDisabled = currentPage >= pages - 1;
61
- const isLeftDisabled = dataLength === 0 || currentPage === 0;
62
- const isRightDisabled = dataLength === 0 || currentPage >= pages - 1;
63
- return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "flex-end", align: "center", flexWrap: "nowrap", children: [(!isServerPagination || paginationSelectOptions) && ((0, jsx_runtime_1.jsx)(Select_1.default, { native: false, isSearchable: false, "aria-label": "Rows per page", onChange: (option) => option && handlePageSizeChange(Number(option.id)), value: rowsPerPage, size: "xs", width: "4.5rem", isDisabled: dataLength === 0, options: computedOptions.map((size) => ({ id: size, label: String(size) })) })), dataLength > 0 && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { w: "100%", children: [(0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "First", placement: "top", children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { "aria-label": "first-page", color: isLeftDisabled ? theme.colors.textMuted : theme.colors.text[500], rounded: "full", variant: isLeftDisabled ? "plain" : "ghost", onClick: () => setCurrentPage(0), _hover: !isLeftDisabled ? { color: theme.colors.accentText } : {}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsLeft, {}) }) }), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Previous", placement: "top", children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { "aria-label": "previous-page", color: isLeftDisabled ? theme.colors.textMuted : theme.colors.text[500], rounded: "full", variant: isLeftDisabled ? "plain" : "ghost", onClick: () => currentPage > 0 && setCurrentPage(currentPage - 1), _hover: !isLeftDisabled ? { color: theme.colors.accentText } : {}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, {}) }) }), (0, jsx_runtime_1.jsx)(react_2.Text, { mt: 3, fontSize: 12, whiteSpace: "nowrap", color: theme.colors.text[500], children: paginationText }), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Next", placement: "top", children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { "aria-label": "next-page", color: isRightDisabled ? theme.colors.textMuted : theme.colors.text[500], rounded: "full", variant: isRightDisabled ? "plain" : "ghost", onClick: () => currentPage < pages - 1 && setCurrentPage(currentPage + 1), _hover: !isRightDisabled ? { color: theme.colors.accentText } : {}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, {}) }) }), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Last", placement: "top", children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { "aria-label": "last-page", color: isRightDisabled ? theme.colors.textMuted : theme.colors.text[500], rounded: "full", variant: isRightDisabled ? "plain" : "ghost", onClick: () => setCurrentPage(pages - 1), _hover: !isRightDisabled ? { color: theme.colors.accentText } : {}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsRight, {}) }) })] }))] }));
61
+ const lastPage = Math.max(0, pages - 1);
62
+ // Stepping back is how a user escapes an empty page, so it survives an empty result.
63
+ const isLeftDisabled = currentPage === 0;
64
+ const isRightDisabled = dataLength === 0 || currentPage >= lastPage;
65
+ const step = (label, ariaLabel, icon, isDisabled, onClick) => ((0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: label, placement: "top", children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { "aria-label": ariaLabel, icon: icon, variant: "ghost", size: "xs", minW: STEP_SIZE, h: STEP_SIZE, rounded: "full", color: isDisabled ? theme.colors.textMuted : theme.colors.text[500], isDisabled: isDisabled, onClick: onClick, _hover: isDisabled ? {} : { color: theme.colors.accentText } }) }));
66
+ return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { justify: "flex-end", align: "center", flexWrap: "nowrap", gap: "0.75rem", children: [(!isServerPagination || paginationSelectOptions) && ((0, jsx_runtime_1.jsx)(Select_1.default, { native: false, isSearchable: false, "aria-label": "Rows per page", onChange: (option) => option && handlePageSizeChange(Number(option.id)), value: rowsPerPage, size: "xs", width: "3.75rem", isDisabled: dataLength === 0, options: computedOptions.map((size) => ({ id: size, label: String(size) })) })), (dataLength > 0 || currentPage > 0) && ((0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: "0.125rem", children: [step("First", "first-page", (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsLeft, { size: STEP_ICON }), isLeftDisabled, () => setCurrentPage(0)), step("Previous", "previous-page", (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { size: STEP_ICON }), isLeftDisabled, () => setCurrentPage((page) => Math.max(0, page - 1))), (0, jsx_runtime_1.jsx)(react_2.Text, { px: "0.375rem", fontSize: "0.75rem", lineHeight: STEP_SIZE, whiteSpace: "nowrap", color: theme.colors.text[500], sx: { fontVariantNumeric: "tabular-nums" }, children: paginationText }), step("Next", "next-page", (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { size: STEP_ICON }), isRightDisabled, () => setCurrentPage((page) => Math.min(lastPage, page + 1))), step("Last", "last-page", (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsRight, { size: STEP_ICON }), isRightDisabled, () => setCurrentPage(lastPage))] }))] }));
64
67
  });
65
68
  exports.default = Pagination;
@@ -1,3 +1,4 @@
1
1
  import { TableBodyPageProps } from "../TableProps";
2
- declare const TableBody: ({ data, isCheckbox, columns, startRow, columnWidths, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe, accentColors, groups, onAddItem, emptyState, }: TableBodyPageProps) => import("react/jsx-runtime").JSX.Element;
2
+ export declare const findFirstVisibleIndex: (offsets: number[], total: number, target: number) => number;
3
+ declare const TableBody: ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWidths, slotWidth, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe, accentColors, groups, onAddItem, emptyState, }: TableBodyPageProps) => import("react/jsx-runtime").JSX.Element;
3
4
  export default TableBody;
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.findFirstVisibleIndex = void 0;
39
40
  const jsx_runtime_1 = require("react/jsx-runtime");
40
41
  const react_1 = require("@chakra-ui/react");
41
42
  const react_2 = __importStar(require("react"));
@@ -56,10 +57,31 @@ const getRowId = (row) => row.id;
56
57
  const getRowContent = (row) => typeof row.content === "function"
57
58
  ? row.content
58
59
  : undefined;
60
+ // Smallest index i in [0, total] where offsets[i+1] > target; offsets is sorted.
61
+ const findFirstVisibleIndex = (offsets, total, target) => {
62
+ let lo = 0;
63
+ let hi = total;
64
+ while (lo < hi) {
65
+ const mid = (lo + hi) >>> 1;
66
+ if (offsets[mid + 1] <= target) {
67
+ lo = mid + 1;
68
+ }
69
+ else {
70
+ hi = mid;
71
+ }
72
+ }
73
+ return lo;
74
+ };
75
+ exports.findFirstVisibleIndex = findFirstVisibleIndex;
59
76
  // Monday-style status cell: the whole cell is filled with the status color and
60
77
  // the label is centered in whichever of white/black clears AA against that fill —
61
78
  // the fill can be a consumer hex we can't know in advance is light or dark.
62
- const StatusCell = ({ value, onSolid }) => ((0, jsx_runtime_1.jsx)(react_1.Box, { display: "flex", alignItems: "center", justifyContent: "center", w: "100%", h: "100%", px: "0.5rem", fontSize: "0.75rem", fontWeight: 600, color: onSolid, textAlign: "center", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", children: value }));
79
+ const BLEED_JUSTIFY = {
80
+ start: "flex-start",
81
+ center: "center",
82
+ end: "flex-end",
83
+ };
84
+ const StatusCell = ({ value, onSolid, align, }) => ((0, jsx_runtime_1.jsx)(react_1.Box, { display: "flex", alignItems: "center", justifyContent: BLEED_JUSTIFY[align !== null && align !== void 0 ? align : "center"], w: "100%", h: "100%", px: "0.5rem", fontSize: "0.75rem", fontWeight: 600, color: onSolid, textAlign: align !== null && align !== void 0 ? align : "center", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", children: value }));
63
85
  // Studio's status cell: a contained chip on the tone's soft rung. Same tone pair
64
86
  // the group header already uses, so an override hex behaves identically in both.
65
87
  const StatusPill = ({ value, soft, ink, radius, }) => ((0, jsx_runtime_1.jsx)(react_1.Box, { display: "inline-flex", alignItems: "center", maxW: "100%", px: "0.5rem", py: "0.125rem", borderRadius: radius, bg: soft, color: ink, fontSize: "0.75rem", fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", children: value }));
@@ -148,13 +170,13 @@ const TableRow = react_2.default.memo(({ row, rowIndex, isChecked, isExpanded, i
148
170
  const bleedBg = statusColor === null || statusColor === void 0 ? void 0 : statusColor.soft;
149
171
  const bleedInk = statusColor === null || statusColor === void 0 ? void 0 : statusColor.text;
150
172
  const canHover = !isBleed && !((cellStyles === null || cellStyles === void 0 ? void 0 : cellStyles[header.id]) || rowStyle);
151
- return ((0, jsx_runtime_1.jsx)(react_1.Td, { maxWidth: 500, minWidth: 120, height: `${rowHeight}px`, py: isBleed ? 0 : cellPy, px: isBleed ? 0 : undefined, onClick: () => handleCellClick(header), fontSize: 14, fontWeight: 400, position: isFrozen ? "sticky" : undefined, left: isFrozen ? leftOffsets[headerIndex] : undefined, zIndex: isFrozen ? 1 : 0, background: isBleed ? bleedBg : isChecked ? (isFrozen ? selectedFrozenBg : selectedBg) : isFrozen ? frozenCellBg : restBg, textAlign: isBleed ? undefined : header.align, textOverflow: "ellipsis", boxShadow: isFirstDataCell ? leadSx : undefined, borderBottom: noBorders
173
+ return ((0, jsx_runtime_1.jsx)(react_1.Td, { maxWidth: 500, minWidth: 120, height: `${rowHeight}px`, py: isBleed ? 0 : cellPy, px: isBleed ? 0 : undefined, onClick: () => handleCellClick(header), fontSize: 14, fontWeight: 400, position: isFrozen ? "sticky" : undefined, left: isFrozen ? `${leftOffsets[headerIndex]}px` : undefined, zIndex: isFrozen ? 1 : 0, background: isBleed ? bleedBg : isChecked ? (isFrozen ? selectedFrozenBg : selectedBg) : isFrozen ? frozenCellBg : restBg, textAlign: isBleed ? undefined : header.align, textOverflow: "ellipsis", boxShadow: isFirstDataCell ? leadSx : undefined, borderBottom: noBorders
152
174
  ? "none"
153
175
  : `0.063rem solid ${isFrozen || isChecked
154
176
  ? variant.row.frozenRuleColor
155
177
  : variant.row.ruleColor}`, className: `columns ${isFrozen ? "sticky-columns" : "scrollable-columns"}`, boxSizing: "border-box", color: isBleed ? bleedInk : variant.cells.ink, "data-hoverable": canHover ? "true" : "false", _hover: variant.row.hoverScope === "row" || !canHover
156
178
  ? EMPTY_HOVER
157
- : hoverStyle, children: isBleed ? ((0, jsx_runtime_1.jsx)(StatusCell, { value: (0, table_1.normalizeTableCellValue)(row[header.id]), onSolid: bleedInk })) : isStatus && statusColor ? ((0, jsx_runtime_1.jsx)(StatusPill, { value: (0, table_1.normalizeTableCellValue)(row[header.id]), soft: statusColor.soft, ink: (0, pillInk_1.resolvePillInk)(statusColor.text, statusColor.soft, theme.colors.background[50]), radius: variant.cells.pillRadius })) : ((0, jsx_runtime_1.jsx)(react_1.Box, { display: "block", overflow: "hidden", whiteSpace: "normal", overflowWrap: "break-word", children: (0, table_1.normalizeTableCellValue)(header.node ? header.node(row) : row[header.id]) })) }, headerIndex + 1));
179
+ : hoverStyle, children: isBleed ? ((0, jsx_runtime_1.jsx)(StatusCell, { value: (0, table_1.normalizeTableCellValue)(row[header.id]), onSolid: bleedInk, align: header.align })) : isStatus && statusColor ? ((0, jsx_runtime_1.jsx)(StatusPill, { value: (0, table_1.normalizeTableCellValue)(row[header.id]), soft: statusColor.soft, ink: (0, pillInk_1.resolvePillInk)(statusColor.text, statusColor.soft, theme.colors.background[50]), radius: variant.cells.pillRadius })) : ((0, jsx_runtime_1.jsx)(react_1.Box, { display: "block", overflow: "hidden", whiteSpace: "normal", overflowWrap: "break-word", children: (0, table_1.normalizeTableCellValue)(header.node ? header.node(row) : row[header.id]) })) }, headerIndex + 1));
158
180
  }), isLink && ((0, jsx_runtime_1.jsx)(react_1.Td, { w: 2, p: 0, fontSize: 14, background: isChecked ? (isActionFreeze ? selectedFrozenBg : selectedBg) : isActionFreeze ? frozenCellBg : restBg, color: freezedTextColor, position: isActionFreeze ? "sticky" : "relative", borderBottom: borderStyle, boxSizing: "border-box", right: 0, zIndex: 1, className: `columns sticky-columns${isActionFreeze ? "-right" : ""}`, children: (row.onLink || row.onEdit || row.onDelete) ? ((0, jsx_runtime_1.jsx)(TableActions_1.default, { row: row })) : null }))] }), rowContent && isExpanded && ((0, jsx_runtime_1.jsx)(react_1.Tr, { ref: expandedRowRef, children: (0, jsx_runtime_1.jsx)(react_1.Td, { colSpan: columns.length +
159
181
  (isCheckbox ? 1 : 0) +
160
182
  (isContent ? 1 : 0) +
@@ -169,7 +191,7 @@ const TableEmpty = ({ emptyState }) => {
169
191
  return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: emptyState });
170
192
  return ((0, jsx_runtime_1.jsx)(EmptyState_1.default, { size: "sm", minH: `${rowMetrics_1.EMPTY_STATE_HEIGHT}px`, title: structured === null || structured === void 0 ? void 0 : structured.title, description: structured === null || structured === void 0 ? void 0 : structured.description, icon: icon }));
171
193
  };
172
- const TableBody = ({ data, isCheckbox, columns, startRow, columnWidths, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe = false, accentColors, groups, onAddItem, emptyState, }) => {
194
+ const TableBody = ({ data, isCheckbox, columns, startRow, rowsPerPage, columnWidths, slotWidth, freezedBgColor, freezedTextColor, noBorders, handleCheckbox, selections, isLoading, onRowClick, isContent, isLink, isActionFreeze, loadingSkeletonRows, scrollContainerRef, density, stripe = false, accentColors, groups, onAddItem, emptyState, }) => {
173
195
  var _a;
174
196
  const theme = (0, useCustomTheme_1.useCustomTheme)();
175
197
  const variant = (0, TableVariantContext_1.useTableVariant)();
@@ -218,7 +240,15 @@ const TableBody = ({ data, isCheckbox, columns, startRow, columnWidths, freezedB
218
240
  const el = scrollContainerRef === null || scrollContainerRef === void 0 ? void 0 : scrollContainerRef.current;
219
241
  if (!el || !(shouldVirtualize || groupVirtualize))
220
242
  return;
221
- const onScroll = () => setScrollTop(el.scrollTop);
243
+ let rafId = null;
244
+ const onScroll = () => {
245
+ if (rafId != null)
246
+ return;
247
+ rafId = requestAnimationFrame(() => {
248
+ rafId = null;
249
+ setScrollTop(el.scrollTop);
250
+ });
251
+ };
222
252
  const measure = () => setViewportH(el.clientHeight);
223
253
  measure();
224
254
  setScrollTop(el.scrollTop);
@@ -227,23 +257,30 @@ const TableBody = ({ data, isCheckbox, columns, startRow, columnWidths, freezedB
227
257
  return () => {
228
258
  el.removeEventListener("scroll", onScroll);
229
259
  window.removeEventListener("resize", measure);
260
+ if (rafId != null)
261
+ cancelAnimationFrame(rafId);
230
262
  };
231
263
  }, [scrollContainerRef, shouldVirtualize, groupVirtualize, data.length, groupRowCount]);
232
- // New page: jump back to the top so the window recomputes from row 0.
264
+ // Reset only on a real page change, never merely from the virtualization
265
+ // threshold flipping or rows being appended (infinite scroll/load-more).
266
+ const sliceKey = `${startRow}|${rowsPerPage}`;
267
+ const prevSliceKeyRef = (0, react_2.useRef)(sliceKey);
233
268
  (0, react_2.useEffect)(() => {
234
- if (!shouldVirtualize)
269
+ const changed = prevSliceKeyRef.current !== sliceKey;
270
+ prevSliceKeyRef.current = sliceKey;
271
+ if (!changed)
235
272
  return;
236
273
  const el = scrollContainerRef === null || scrollContainerRef === void 0 ? void 0 : scrollContainerRef.current;
237
274
  if (el)
238
275
  el.scrollTop = 0;
239
276
  setScrollTop(0);
240
- }, [startRow, shouldVirtualize, scrollContainerRef]);
277
+ }, [sliceKey, scrollContainerRef]);
241
278
  const total = data.length;
242
279
  // Prefix-sum offsets using measured heights (exact for rendered rows) and
243
280
  // estimates for not-yet-measured rows (ROW_HEIGHT, + ESTIMATED_EXPAND if expanded).
244
281
  // Independent of scrollTop, so scrolling reuses it instead of rebuilding it.
245
282
  const offsets = (0, react_2.useMemo)(() => {
246
- if (!shouldVirtualize)
283
+ if (!shouldVirtualize || groups)
247
284
  return null;
248
285
  const acc = new Array(total + 1);
249
286
  acc[0] = 0;
@@ -256,7 +293,7 @@ const TableBody = ({ data, isCheckbox, columns, startRow, columnWidths, freezedB
256
293
  acc[i + 1] = acc[i] + h;
257
294
  }
258
295
  return acc;
259
- }, [shouldVirtualize, total, data, measuredHeights, expandedRows, startRow, ROW_HEIGHT, ESTIMATED_EXPAND]);
296
+ }, [shouldVirtualize, groups, total, data, measuredHeights, expandedRows, startRow, ROW_HEIGHT, ESTIMATED_EXPAND]);
260
297
  let firstIndex = 0;
261
298
  let lastIndex = total;
262
299
  let topPad = 0;
@@ -266,9 +303,7 @@ const TableBody = ({ data, isCheckbox, columns, startRow, columnWidths, freezedB
266
303
  const totalH = offsets[total];
267
304
  const top = scrollTop - OVERSCAN * ROW_HEIGHT;
268
305
  const bottom = scrollTop + vh + OVERSCAN * ROW_HEIGHT;
269
- firstIndex = 0;
270
- while (firstIndex < total && offsets[firstIndex + 1] <= top)
271
- firstIndex++;
306
+ firstIndex = (0, exports.findFirstVisibleIndex)(offsets, total, top);
272
307
  lastIndex = firstIndex;
273
308
  while (lastIndex < total && offsets[lastIndex] < bottom)
274
309
  lastIndex++;
@@ -303,9 +338,9 @@ const TableBody = ({ data, isCheckbox, columns, startRow, columnWidths, freezedB
303
338
  (isContent ? 1 : 0) +
304
339
  (isLink ? 1 : 0), [columns, isCheckbox, isContent, isLink]);
305
340
  const leftOffsets = (0, react_2.useMemo)(() => {
306
- const checkboxOffset = isCheckbox ? rowMetrics_1.CHECKBOX_SLOT_OFFSET : 0;
341
+ const checkboxOffset = isCheckbox ? slotWidth : 0;
307
342
  return columnWidths.map((_, idx) => columnWidths.slice(0, idx).reduce((sum, w) => sum + w, 0) + checkboxOffset);
308
- }, [columnWidths, isCheckbox]);
343
+ }, [columnWidths, isCheckbox, slotWidth]);
309
344
  const borderStyle = (0, react_2.useMemo)(() => noBorders ? "none" : `0.063rem solid ${variant.row.ruleColor}`, [noBorders, variant.row.ruleColor]);
310
345
  // Selected-row tint applied per-cell (NOT a blanket `& td` override) so status
311
346
  // cells keep their solid color + white text instead of white-on-tint.
@@ -342,6 +377,38 @@ const TableBody = ({ data, isCheckbox, columns, startRow, columnWidths, freezedB
342
377
  }
343
378
  return { heights, offsets, totalH: acc };
344
379
  }, [groupItems, groupVirtualize, rowHeight]);
380
+ // Clamp scrollTop back when a shorter data set leaves it past the new content height.
381
+ (0, react_2.useEffect)(() => {
382
+ const totalH = shouldVirtualize && offsets ? offsets[total] : groupVirtualize && groupMetrics ? groupMetrics.totalH : null;
383
+ if (totalH == null)
384
+ return;
385
+ const maxScrollTop = Math.max(0, totalH - viewportH);
386
+ if (scrollTop > maxScrollTop) {
387
+ const el = scrollContainerRef === null || scrollContainerRef === void 0 ? void 0 : scrollContainerRef.current;
388
+ if (el)
389
+ el.scrollTop = maxScrollTop;
390
+ setScrollTop(maxScrollTop);
391
+ }
392
+ }, [shouldVirtualize, offsets, total, groupVirtualize, groupMetrics, viewportH, scrollTop, scrollContainerRef]);
393
+ // Prune measured heights for rows no longer in data so the map stays bounded.
394
+ (0, react_2.useEffect)(() => {
395
+ setMeasuredHeights((prev) => {
396
+ if (prev.size === 0)
397
+ return prev;
398
+ const ids = new Set(data.map(getRowId));
399
+ let changed = false;
400
+ const next = new Map();
401
+ prev.forEach((h, id) => {
402
+ if (ids.has(id)) {
403
+ next.set(id, h);
404
+ }
405
+ else {
406
+ changed = true;
407
+ }
408
+ });
409
+ return changed ? next : prev;
410
+ });
411
+ }, [data]);
345
412
  if (isLoading && data.length === 0) {
346
413
  return ((0, jsx_runtime_1.jsx)(TableLoading_1.TableBodyLoading, { noOfColumns: totalVisibleColumns, borderColor: theme.colors.boxborder[200], rowsCount: typeof loadingSkeletonRows === "number" ? loadingSkeletonRows : 4, rowHeight: rowHeight }));
347
414
  }
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import { TableHeaderPageProps } from "../TableProps";
3
- declare const _default: React.MemoExoticComponent<({ columns, isCheckbox, handleSort, headerRefs, columnWidths, columnsSort, freezedTextColor, noBorders, handleCheckbox, checked, isLoading, isContent, isLink, isActionFreeze, setColumnsSearch, columnsSearch, isSelecting, }: TableHeaderPageProps) => import("react/jsx-runtime").JSX.Element>;
3
+ declare const _default: React.MemoExoticComponent<({ columns, isCheckbox, handleSort, headerRefs, columnWidths, slotRef, slotWidth, columnsSort, freezedTextColor, noBorders, handleCheckbox, checked, isLoading, isContent, isLink, isActionFreeze, setColumnsSearch, columnsSearch, isSelecting, }: TableHeaderPageProps) => import("react/jsx-runtime").JSX.Element>;
4
4
  export default _default;
@@ -13,24 +13,23 @@ const TableFilters_1 = __importDefault(require("../filters/TableFilters"));
13
13
  const lucide_react_1 = require("lucide-react");
14
14
  const Checkbox_1 = __importDefault(require("../../Checkbox/Checkbox"));
15
15
  const TableVariantContext_1 = require("../variants/TableVariantContext");
16
- const rowMetrics_1 = require("./rowMetrics");
17
16
  const motion_1 = require("../../../Theme/tokens/primitives/motion");
18
17
  const motion_2 = require("../../../Theme/chakra/motion");
19
18
  const focusRing_styles_1 = require("../../../Theme/chakra/focusRing.styles");
20
19
  const LABEL_JUSTIFY = { start: "space-between", center: "center", end: "flex-start" };
21
- const TableHeader = ({ columns, isCheckbox, handleSort, headerRefs, columnWidths, columnsSort, freezedTextColor, noBorders, handleCheckbox, checked, isLoading, isContent, isLink, isActionFreeze, setColumnsSearch, columnsSearch, isSelecting, }) => {
20
+ const TableHeader = ({ columns, isCheckbox, handleSort, headerRefs, columnWidths, slotRef, slotWidth, columnsSort, freezedTextColor, noBorders, handleCheckbox, checked, isLoading, isContent, isLink, isActionFreeze, setColumnsSearch, columnsSearch, isSelecting, }) => {
22
21
  const [openFilterId, setOpenFilterId] = (0, react_2.useState)(null);
23
- return ((0, jsx_runtime_1.jsxs)(react_1.Tr, { pr: 0, "aria-rowindex": 1, children: [isContent && (0, jsx_runtime_1.jsx)(ContentSpacerCell, { noBorders: noBorders }), isCheckbox && ((0, jsx_runtime_1.jsx)(SelectAllCell, { freezedTextColor: freezedTextColor, noBorders: noBorders, isSelecting: isSelecting, isLoading: isLoading, checked: checked, handleCheckbox: handleCheckbox })), columns.map((header, index) => {
22
+ return ((0, jsx_runtime_1.jsxs)(react_1.Tr, { pr: 0, "aria-rowindex": 1, children: [isContent && (0, jsx_runtime_1.jsx)(ContentSpacerCell, { noBorders: noBorders }), isCheckbox && ((0, jsx_runtime_1.jsx)(SelectAllCell, { slotRef: slotRef, freezedTextColor: freezedTextColor, noBorders: noBorders, isSelecting: isSelecting, isLoading: isLoading, checked: checked, handleCheckbox: handleCheckbox })), columns.map((header, index) => {
24
23
  if (header === null || header === void 0 ? void 0 : header.isHidden)
25
24
  return null;
26
- return ((0, jsx_runtime_1.jsx)(ColumnHeader, { header: header, index: index, columnWidths: columnWidths, handleSort: handleSort, isCheckbox: isCheckbox, noBorders: noBorders, columnsSort: columnsSort, headerRefs: headerRefs, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch, openFilterId: openFilterId, setOpenFilterId: setOpenFilterId }, header.label));
25
+ return ((0, jsx_runtime_1.jsx)(ColumnHeader, { header: header, index: index, columnWidths: columnWidths, slotWidth: slotWidth, handleSort: handleSort, isCheckbox: isCheckbox, noBorders: noBorders, columnsSort: columnsSort, headerRefs: headerRefs, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch, openFilterId: openFilterId, setOpenFilterId: setOpenFilterId }, header.label));
27
26
  }), isLink && (0, jsx_runtime_1.jsx)(ViewSpacerCell, { noBorders: noBorders, isActionFreeze: isActionFreeze })] }));
28
27
  };
29
28
  exports.default = (0, react_2.memo)(TableHeader);
30
- const SelectAllCell = (0, react_2.memo)(({ freezedTextColor, noBorders, isSelecting, isLoading, checked, handleCheckbox, }) => {
29
+ const SelectAllCell = (0, react_2.memo)(({ slotRef, freezedTextColor, noBorders, isSelecting, isLoading, checked, handleCheckbox, }) => {
31
30
  const theme = (0, useCustomTheme_1.useCustomTheme)();
32
31
  const variant = (0, TableVariantContext_1.useTableVariant)();
33
- return ((0, jsx_runtime_1.jsx)(react_1.Th, { scope: "col", w: variant.slot.width, minW: variant.slot.width, px: variant.slot.centered ? 0 : undefined, textAlign: variant.slot.centered ? "center" : undefined, fontSize: 14, fontWeight: 600, color: freezedTextColor, textTransform: "capitalize", backgroundColor: variant.header.bg, position: "sticky", className: `columns sticky-columns`, left: "0px", zIndex: 9, borderBottom: noBorders ? "none" : `0.063rem solid ${variant.header.ruleColor}`, children: (0, jsx_runtime_1.jsx)(react_1.Box, { display: "flex", alignItems: "center", justifyContent: "center", children: isSelecting ? ((0, jsx_runtime_1.jsx)(react_1.Spinner, { size: "sm", color: theme.colors.accentText })) : ((0, jsx_runtime_1.jsx)(Checkbox_1.default, { "aria-label": "Select all rows", variant: "outline", onChange: () => handleCheckbox(0), isChecked: isLoading ? false : checked === true, isIndeterminate: checked === "indeterminate" })) }) }));
32
+ return ((0, jsx_runtime_1.jsx)(react_1.Th, { ref: slotRef, scope: "col", w: variant.slot.width, minW: variant.slot.width, px: variant.slot.centered ? 0 : undefined, textAlign: variant.slot.centered ? "center" : undefined, fontSize: 14, fontWeight: 600, color: freezedTextColor, textTransform: "capitalize", backgroundColor: variant.header.bg, position: "sticky", className: `columns sticky-columns`, left: "0px", zIndex: 9, borderBottom: noBorders ? "none" : `0.063rem solid ${variant.header.ruleColor}`, children: (0, jsx_runtime_1.jsx)(react_1.Box, { display: "flex", alignItems: "center", justifyContent: "center", children: isSelecting ? ((0, jsx_runtime_1.jsx)(react_1.Spinner, { size: "sm", color: theme.colors.accentText })) : ((0, jsx_runtime_1.jsx)(Checkbox_1.default, { "aria-label": "Select all rows", variant: "outline", onChange: () => handleCheckbox(0), isChecked: isLoading ? false : checked === true, isIndeterminate: checked === "indeterminate" })) }) }));
34
33
  });
35
34
  const ContentSpacerCell = (0, react_2.memo)(({ noBorders }) => {
36
35
  const variant = (0, TableVariantContext_1.useTableVariant)();
@@ -48,7 +47,7 @@ const SortingIcon = (0, react_2.memo)(({ label, sortState, isVisible, handleSort
48
47
  color: (0, accentText_1.accentTextOnCanvas)(theme.colors.secondary, variant.header.bg),
49
48
  }, children: [sortState === "none" && (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { size: 14 }), sortState === "asc" && (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronUp, { size: 14 }), sortState === "desc" && (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 14 })] }));
50
49
  });
51
- const ColumnHeader = (0, react_2.memo)(({ header, index, columnWidths, isCheckbox, noBorders, columnsSort, headerRefs, columnsSearch, setColumnsSearch, handleSort, openFilterId, setOpenFilterId, }) => {
50
+ const ColumnHeader = (0, react_2.memo)(({ header, index, columnWidths, slotWidth, isCheckbox, noBorders, columnsSort, headerRefs, columnsSearch, setColumnsSearch, handleSort, openFilterId, setOpenFilterId, }) => {
52
51
  var _a, _b;
53
52
  const [isHovered, setIsHovered] = (0, react_2.useState)(false);
54
53
  const theme = (0, useCustomTheme_1.useCustomTheme)();
@@ -56,7 +55,7 @@ const ColumnHeader = (0, react_2.memo)(({ header, index, columnWidths, isCheckbo
56
55
  const isFrozen = header.isFreeze;
57
56
  const align = (_a = header.align) !== null && _a !== void 0 ? _a : "start";
58
57
  const leftOffset = isFrozen
59
- ? (0, table_1.calculateLeftOffset)(columnWidths, index) + (isCheckbox ? rowMetrics_1.CHECKBOX_SLOT_OFFSET : 0)
58
+ ? (0, table_1.calculateLeftOffset)(columnWidths, index) + (isCheckbox ? slotWidth : 0)
60
59
  : undefined;
61
60
  const isSorting = header.isSort
62
61
  ? columnsSort.find((o) => o.column === header.id)
@@ -95,7 +94,7 @@ const ColumnHeader = (0, react_2.memo)(({ header, index, columnWidths, isCheckbo
95
94
  isSortActive ||
96
95
  openFilterId === header.id ||
97
96
  !!columnsSearch[header.id];
98
- return ((0, jsx_runtime_1.jsx)(react_1.Th, { scope: "col", ref: (el) => (headerRefs.current[index] = el), "aria-sort": ariaSort, textAlign: align, textTransform: variant.header.textTransform, fontSize: variant.header.fontSize, fontWeight: variant.header.fontWeight, letterSpacing: variant.header.letterSpacing, position: isFrozen ? "sticky" : undefined, left: isFrozen ? leftOffset : undefined, minWidth: 120, backgroundColor: variant.header.bg, maxWidth: 500, zIndex: isFrozen ? 2 : 1, py: 2, pr: align === "end" ? undefined : 0, overflow: "hidden", borderTop: "none", borderBottom: noBorders ? "none" : `0.063rem solid ${variant.header.ruleColor}`, borderRight: noBorders || !variant.header.showColumnRule
97
+ return ((0, jsx_runtime_1.jsx)(react_1.Th, { scope: "col", ref: (el) => (headerRefs.current[index] = el), "aria-sort": ariaSort, textAlign: align, textTransform: variant.header.textTransform, fontSize: variant.header.fontSize, fontWeight: variant.header.fontWeight, letterSpacing: variant.header.letterSpacing, position: isFrozen ? "sticky" : undefined, left: isFrozen ? `${leftOffset}px` : undefined, minWidth: 120, backgroundColor: variant.header.bg, maxWidth: 500, zIndex: isFrozen ? 2 : 1, py: 2, pr: align === "end" ? undefined : 0, overflow: "hidden", borderTop: "none", borderBottom: noBorders ? "none" : `0.063rem solid ${variant.header.ruleColor}`, borderRight: noBorders || !variant.header.showColumnRule
99
98
  ? "none"
100
99
  : `0.063rem solid ${variant.header.ruleColor}`, className: `columns ${isFrozen ? "sticky-columns" : "scrollable-columns"}`, height: variant.header.height, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: (0, jsx_runtime_1.jsxs)(react_1.Box, { display: "flex", color: variant.header.ink, lineHeight: "1.25rem", justifyContent: LABEL_JUSTIFY[align], flexDirection: align === "end" ? "row-reverse" : "row", gap: align === "end" ? 1 : undefined, alignItems: "center", position: "relative", children: [(0, jsx_runtime_1.jsx)(react_1.Box, { textAlign: align, textOverflow: "ellipsis", whiteSpace: "nowrap", display: "inline-block", overflow: "hidden", children: header.label }), (0, jsx_runtime_1.jsxs)(react_1.Box, { display: "inline-flex", alignItems: "center", opacity: showActions ? 1 : 0, transition: (0, motion_1.transitionFor)(["opacity"], "base"), sx: (0, motion_2.reducedMotionSx)(), _focusWithin: { opacity: 1 }, children: [header.isSort && ((0, jsx_runtime_1.jsx)(SortingIcon, { label: String(header.label), sortState: (_b = isSorting === null || isSorting === void 0 ? void 0 : isSorting.direction) !== null && _b !== void 0 ? _b : "none", isVisible: isHovered || isSortActive, handleSortClick: () => {
101
100
  let direction = "asc";
@@ -6,6 +6,5 @@ export declare const DENSITY_ROWS: Record<TableDensity, {
6
6
  export declare const TOOLBAR_HEIGHT = 45;
7
7
  export declare const GROUP_HEADER_HEIGHT = 42;
8
8
  export declare const EMPTY_STATE_HEIGHT = 200;
9
- export declare const CHECKBOX_SLOT_OFFSET = 50;
10
9
  export declare const rowHeightFor: (density: TableDensity | undefined) => number;
11
10
  export declare const pxOf: (length: string) => number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pxOf = exports.rowHeightFor = exports.CHECKBOX_SLOT_OFFSET = exports.EMPTY_STATE_HEIGHT = exports.GROUP_HEADER_HEIGHT = exports.TOOLBAR_HEIGHT = exports.DENSITY_ROWS = void 0;
3
+ exports.pxOf = exports.rowHeightFor = exports.EMPTY_STATE_HEIGHT = exports.GROUP_HEADER_HEIGHT = exports.TOOLBAR_HEIGHT = exports.DENSITY_ROWS = void 0;
4
4
  exports.DENSITY_ROWS = {
5
5
  compact: { height: 32, py: "0.125rem" },
6
6
  normal: { height: 40, py: "0.5rem" },
@@ -9,7 +9,6 @@ exports.DENSITY_ROWS = {
9
9
  exports.TOOLBAR_HEIGHT = 45;
10
10
  exports.GROUP_HEADER_HEIGHT = 42;
11
11
  exports.EMPTY_STATE_HEIGHT = 200;
12
- exports.CHECKBOX_SLOT_OFFSET = 50;
13
12
  const rowHeightFor = (density) => exports.DENSITY_ROWS[density !== null && density !== void 0 ? density : "normal"].height;
14
13
  exports.rowHeightFor = rowHeightFor;
15
14
  const pxOf = (length) => parseFloat(length);
@@ -12,17 +12,21 @@ type UseTableProps = {
12
12
  totalRecords: number;
13
13
  onNoOfRowsPerPageChange?: (noOfRows: number) => void;
14
14
  defaultVisibleColumns?: number;
15
+ columnsSearch: Record<string, string>;
15
16
  };
16
17
  export declare const DEFAULT_PAGE_SIZE = 20;
17
- declare const useTable: ({ data, isPagination, selections, onSelection, tablePreferences, savePreferences, noOfRowsPerPage, isServerPagination, totalRecords, columns, onNoOfRowsPerPageChange, defaultVisibleColumns, }: UseTableProps) => {
18
+ declare const useTable: ({ data, isPagination, selections, onSelection, tablePreferences, savePreferences, noOfRowsPerPage, isServerPagination, totalRecords, columns, onNoOfRowsPerPageChange, defaultVisibleColumns, columnsSearch, }: UseTableProps) => {
18
19
  tableData: DataObject[];
19
20
  isContent: boolean;
20
21
  isLink: boolean;
21
22
  headerRefs: import("react").MutableRefObject<(HTMLTableCellElement | null)[]>;
22
23
  columnWidths: number[];
24
+ slotRef: (node: HTMLTableCellElement | null) => void;
25
+ slotWidth: number;
23
26
  handleSort: (field: string | number, sort: "asc" | "desc" | "none") => void;
24
27
  handleCheckbox: (id: string | number) => void;
25
28
  filteredData: DataObject[];
29
+ searchedData: DataObject[];
26
30
  startRow: number;
27
31
  endRow: number;
28
32
  selection: (string | number)[];
@@ -18,10 +18,25 @@ const mergeColumnWithPreference = (pref, originalCol) => {
18
18
  }
19
19
  return next;
20
20
  };
21
- const useTable = ({ data, isPagination, selections, onSelection, tablePreferences, savePreferences, noOfRowsPerPage, isServerPagination, totalRecords, columns, onNoOfRowsPerPageChange, defaultVisibleColumns, }) => {
21
+ const useTable = ({ data, isPagination, selections, onSelection, tablePreferences, savePreferences, noOfRowsPerPage, isServerPagination, totalRecords, columns, onNoOfRowsPerPageChange, defaultVisibleColumns, columnsSearch, }) => {
22
22
  var _a, _b;
23
23
  const headerRefs = (0, react_1.useRef)([]);
24
+ const slotObserver = (0, react_1.useRef)(null);
24
25
  const [columnWidths, setColumnWidths] = (0, react_1.useState)([]);
26
+ const [slotWidth, setSlotWidth] = (0, react_1.useState)(0);
27
+ // Frozen columns butt against the select-all cell, so its width is watched, not assumed.
28
+ const slotRef = (0, react_1.useCallback)((node) => {
29
+ var _a;
30
+ (_a = slotObserver.current) === null || _a === void 0 ? void 0 : _a.disconnect();
31
+ slotObserver.current = null;
32
+ if (!node) {
33
+ setSlotWidth(0);
34
+ return;
35
+ }
36
+ const observer = new ResizeObserver(() => setSlotWidth(node.offsetWidth));
37
+ observer.observe(node);
38
+ slotObserver.current = observer;
39
+ }, []);
25
40
  const [selection, setSelection] = (0, react_1.useState)(selections !== null && selections !== void 0 ? selections : []);
26
41
  const [columnsSort, setColumnsSort] = (0, react_1.useState)([]);
27
42
  const [currentPage, setCurrentPage] = (0, react_1.useState)(0);
@@ -83,27 +98,45 @@ const useTable = ({ data, isPagination, selections, onSelection, tablePreference
83
98
  if (!isServerPagination)
84
99
  setCurrentPage(0);
85
100
  }, [tableData.length, isServerPagination]);
101
+ const hasColumnSearch = (0, react_1.useMemo)(() => Object.values(columnsSearch).some((value) => value.trim() !== ""), [columnsSearch]);
102
+ // Column search runs before the page slice, or it would only ever see one page.
103
+ const searchedData = (0, react_1.useMemo)(() => (hasColumnSearch ? (0, table_1.searchAndSortData)(tableData, columnsSearch) : tableData), [hasColumnSearch, columnsSearch, tableData]);
86
104
  const pages = (0, react_1.useMemo)(() => {
87
105
  if (isServerPagination) {
88
106
  return Math.ceil(totalRecords / (rowsPerPage || 1));
89
107
  }
90
- return isPagination ? Math.ceil(tableData.length / rowsPerPage) : 0;
108
+ return isPagination ? Math.ceil(searchedData.length / rowsPerPage) : 0;
91
109
  }, [
92
110
  isPagination,
93
111
  rowsPerPage,
94
- tableData.length,
112
+ searchedData.length,
95
113
  totalRecords,
96
114
  isServerPagination,
97
115
  ]);
98
116
  const startRow = (0, react_1.useMemo)(() => (!isPagination ? 0 : currentPage * rowsPerPage), [currentPage, isPagination, rowsPerPage]);
99
- const endRow = (0, react_1.useMemo)(() => (!isPagination ? tableData.length : startRow + rowsPerPage), [isPagination, rowsPerPage, startRow, tableData.length]);
117
+ const endRow = (0, react_1.useMemo)(() => (!isPagination ? searchedData.length : startRow + rowsPerPage), [isPagination, rowsPerPage, startRow, searchedData.length]);
100
118
  const filteredData = (0, react_1.useMemo)(() => {
101
119
  if (isServerPagination) {
102
- return tableData;
120
+ return searchedData;
103
121
  }
104
- const sortedData = (0, table_1.SortMultiColumnData)(tableData, columnsSort);
122
+ const sortedData = (0, table_1.SortMultiColumnData)(searchedData, columnsSort);
105
123
  return sortedData.slice(startRow, endRow);
106
- }, [columnsSort, startRow, endRow, tableData, isServerPagination]);
124
+ }, [columnsSort, startRow, endRow, searchedData, isServerPagination]);
125
+ const searchKey = (0, react_1.useMemo)(() => JSON.stringify(columnsSearch), [columnsSearch]);
126
+ const prevSearchKey = (0, react_1.useRef)(searchKey);
127
+ (0, react_1.useEffect)(() => {
128
+ if (prevSearchKey.current === searchKey)
129
+ return;
130
+ prevSearchKey.current = searchKey;
131
+ setCurrentPage(0);
132
+ }, [searchKey]);
133
+ // A narrower result set can strand the user on a page that no longer exists.
134
+ (0, react_1.useEffect)(() => {
135
+ if (isServerPagination || !isPagination)
136
+ return;
137
+ const lastPage = Math.max(0, pages - 1);
138
+ setCurrentPage((page) => Math.min(page, lastPage));
139
+ }, [pages, isServerPagination, isPagination]);
107
140
  const handlePageSizeChange = (size) => {
108
141
  const value = Number(size);
109
142
  setRowsPerPage(value !== 0 ? value : defaultPageSize);
@@ -198,9 +231,12 @@ const useTable = ({ data, isPagination, selections, onSelection, tablePreference
198
231
  isLink,
199
232
  headerRefs,
200
233
  columnWidths,
234
+ slotRef,
235
+ slotWidth,
201
236
  handleSort,
202
237
  handleCheckbox,
203
238
  filteredData,
239
+ searchedData,
204
240
  startRow,
205
241
  endRow,
206
242
  selection,