pixelize-design-library 2.4.2-beta.46 → 2.4.2-beta.48

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 (63) hide show
  1. package/CHANGELOG.md +76 -3
  2. package/COMPONENT-GOTCHAS.md +76 -7
  3. package/dist/Components/Common/chipTriggerSize.d.ts +10 -0
  4. package/dist/Components/Common/chipTriggerSize.js +15 -0
  5. package/dist/Components/Dropdown/DropDown.js +10 -8
  6. package/dist/Components/SearchSelect/SearchSelect.js +66 -20
  7. package/dist/Components/SearchSelect/SearchSelectProps.d.ts +5 -1
  8. package/dist/Components/SearchSelect/searchSelectSize.d.ts +0 -5
  9. package/dist/Components/SearchSelect/searchSelectSize.js +0 -12
  10. package/dist/Components/Select/SelectNative.d.ts +2 -0
  11. package/dist/Components/Select/SelectPopup.js +104 -43
  12. package/dist/Components/Select/SelectProps.d.ts +4 -0
  13. package/dist/Components/Select/optionRowInk.d.ts +4 -0
  14. package/dist/Components/Select/optionRowInk.js +12 -1
  15. package/dist/Components/SelectSearch/SelectSearch.js +8 -4
  16. package/dist/Components/SelectV2/SelectV2.d.ts +2 -9
  17. package/dist/Components/SelectV2/SelectV2.js +2 -10
  18. package/dist/Components/Switch/Switch.d.ts +1 -1
  19. package/dist/Components/Switch/Switch.js +4 -2
  20. package/dist/Components/Table/Table.js +6 -7
  21. package/dist/Components/Table/TableProps.d.ts +9 -1
  22. package/dist/Components/Table/components/Pagination.js +8 -5
  23. package/dist/Components/Table/components/TableBody.d.ts +2 -1
  24. package/dist/Components/Table/components/TableBody.js +82 -15
  25. package/dist/Components/Table/components/TableHeader.d.ts +1 -1
  26. package/dist/Components/Table/components/TableHeader.js +8 -9
  27. package/dist/Components/Table/components/rowMetrics.d.ts +0 -1
  28. package/dist/Components/Table/components/rowMetrics.js +1 -2
  29. package/dist/Components/Table/filters/CompactSelect.js +6 -3
  30. package/dist/Components/Table/hooks/useTable.d.ts +5 -1
  31. package/dist/Components/Table/hooks/useTable.js +43 -7
  32. package/dist/Theme/chakra/Menu.styles.d.ts +6 -0
  33. package/dist/Theme/chakra/Menu.styles.js +8 -0
  34. package/dist/Theme/chakra/componentStyles.d.ts +6 -0
  35. package/dist/Theme/tokens/builders/outlineRung.d.ts +2 -0
  36. package/dist/Theme/tokens/builders/outlineRung.js +14 -1
  37. package/dist/Theme/tokens/builders/rowStates.js +3 -4
  38. package/dist/Theme/tokens/primitives/optionRowSizes.d.ts +20 -0
  39. package/dist/Theme/tokens/primitives/optionRowSizes.js +29 -0
  40. package/dist/esm/Components/Common/chipTriggerSize.js +11 -0
  41. package/dist/esm/Components/Dropdown/DropDown.js +10 -8
  42. package/dist/esm/Components/SearchSelect/SearchSelect.js +66 -20
  43. package/dist/esm/Components/SearchSelect/searchSelectSize.js +0 -12
  44. package/dist/esm/Components/Select/SelectPopup.js +105 -44
  45. package/dist/esm/Components/Select/optionRowInk.js +9 -0
  46. package/dist/esm/Components/SelectSearch/SelectSearch.js +9 -5
  47. package/dist/esm/Components/SelectV2/SelectV2.js +2 -9
  48. package/dist/esm/Components/Switch/Switch.js +4 -2
  49. package/dist/esm/Components/Table/Table.js +6 -7
  50. package/dist/esm/Components/Table/components/Pagination.js +8 -5
  51. package/dist/esm/Components/Table/components/TableBody.js +81 -16
  52. package/dist/esm/Components/Table/components/TableHeader.js +8 -9
  53. package/dist/esm/Components/Table/components/rowMetrics.js +0 -1
  54. package/dist/esm/Components/Table/filters/CompactSelect.js +7 -4
  55. package/dist/esm/Components/Table/hooks/useTable.js +44 -8
  56. package/dist/esm/Theme/chakra/Menu.styles.js +8 -0
  57. package/dist/esm/Theme/tokens/builders/outlineRung.js +12 -0
  58. package/dist/esm/Theme/tokens/builders/rowStates.js +4 -5
  59. package/dist/esm/Theme/tokens/primitives/optionRowSizes.js +25 -0
  60. package/dist/esm/index.js +2 -0
  61. package/dist/index.d.ts +1 -0
  62. package/dist/index.js +9 -4
  63. package/package.json +1 -1
@@ -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);
@@ -6,10 +6,13 @@ const react_2 = require("@chakra-ui/react");
6
6
  const lucide_react_1 = require("lucide-react");
7
7
  const useCustomTheme_1 = require("../../../Theme/useCustomTheme");
8
8
  const rowStates_1 = require("../../../Theme/tokens/builders/rowStates");
9
+ const optionRowSizes_1 = require("../../../Theme/tokens/primitives/optionRowSizes");
10
+ const optionRowInk_1 = require("../../Select/optionRowInk");
9
11
  const useExclusiveOverlay_1 = require("../../../Hooks/useExclusiveOverlay");
10
12
  const CompactSelect = ({ options, value, placeholder = "Select", height = "22px", onSelect, size = "xs" }) => {
11
13
  const theme = (0, useCustomTheme_1.useCustomTheme)();
12
14
  const rowBackgrounds = (0, react_1.useMemo)(() => (0, rowStates_1.listRowBackgrounds)(theme.colors), [theme.colors]);
15
+ const spec = (0, optionRowSizes_1.optionRowSpec)("xs");
13
16
  const [isOpen, setIsOpen] = (0, react_1.useState)(false);
14
17
  const requestExclusiveOpen = (0, useExclusiveOverlay_1.useExclusiveOverlay)(isOpen, () => setIsOpen(false));
15
18
  const openDropdown = () => {
@@ -63,15 +66,15 @@ const CompactSelect = ({ options, value, placeholder = "Select", height = "22px"
63
66
  : isActive
64
67
  ? rowBackgrounds.active
65
68
  : theme.colors.transparent;
66
- return ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "button", type: "button", role: "option", "aria-selected": selected, tabIndex: isActive ? 0 : -1, ref: (el) => {
69
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "button", type: "button", role: "option", "aria-selected": selected, tabIndex: isActive ? 0 : -1, ref: (el) => {
67
70
  optionRefs.current[i] = el;
68
- }, w: "100%", textAlign: "left", border: "none", fontFamily: "inherit", px: 2, py: 1, cursor: "pointer", color: theme.colors.text[700], _hover: {
71
+ }, w: `calc(100% - 2 * ${spec.mx})`, minH: spec.minH, textAlign: "left", border: "none", fontFamily: "inherit", display: "flex", alignItems: "center", gap: spec.gap, mx: spec.mx, px: spec.px, py: spec.py, borderRadius: optionRowSizes_1.OPTION_ROW_RADIUS, cursor: "pointer", color: (0, optionRowInk_1.optionRowInk)(theme.colors, selected), fontSize: spec.fontSize, fontWeight: (0, optionRowInk_1.optionRowWeight)(selected), _hover: {
69
72
  bg: selected ? rowBackgrounds.selectedActive : rowBackgrounds.active,
70
73
  }, bg: rowBg, onClick: () => {
71
74
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(opt);
72
75
  setIsOpen(false);
73
76
  setQuery("");
74
- }, children: (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.75rem", children: opt.label }) }, opt.id));
77
+ }, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { flex: 1, minW: 0, isTruncated: true, children: opt.label }), selected && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 }))] }, opt.id));
75
78
  }), filtered.length === 0 && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 2, py: 2, children: (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.75rem", color: theme.colors.textMuted, children: "No results" }) }))] })] }) }) }) })] }));
76
79
  };
77
80
  exports.default = CompactSelect;
@@ -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,
@@ -18,6 +18,12 @@ export declare const Menu: {
18
18
  borderColor: string;
19
19
  };
20
20
  item: {
21
+ minH: string;
22
+ px: string;
23
+ py: string;
24
+ mx: string;
25
+ fontSize: string;
26
+ borderRadius: string;
21
27
  bg: string;
22
28
  _focus: {
23
29
  [$itemBg.variable]: string;
@@ -4,7 +4,9 @@ exports.Menu = void 0;
4
4
  const react_1 = require("@chakra-ui/react");
5
5
  const anatomy_1 = require("@chakra-ui/anatomy");
6
6
  const rowStates_1 = require("../tokens/builders/rowStates");
7
+ const optionRowSizes_1 = require("../tokens/primitives/optionRowSizes");
7
8
  const paletteFromTheme_1 = require("./paletteFromTheme");
9
+ const itemRowSpec = optionRowSizes_1.OPTION_ROW_SIZES.sm;
8
10
  const { defineMultiStyleConfig, definePartsStyle } = (0, react_1.createMultiStyleConfigHelpers)(anatomy_1.menuAnatomy.keys);
9
11
  const $bg = (0, react_1.cssVar)("menu-bg");
10
12
  // NOTE: fixes Chakra's MenuList/MenuItem both hardcoding --menu-bg to white via dead _dark — item needs its own var too, or hover snaps back to white.
@@ -24,6 +26,12 @@ const baseStyle = definePartsStyle(({ theme }) => {
24
26
  borderColor: colors.boxborder[200],
25
27
  },
26
28
  item: {
29
+ minH: itemRowSpec.minH,
30
+ px: itemRowSpec.px,
31
+ py: itemRowSpec.py,
32
+ mx: itemRowSpec.mx,
33
+ fontSize: itemRowSpec.fontSize,
34
+ borderRadius: optionRowSizes_1.OPTION_ROW_RADIUS,
27
35
  bg: "transparent",
28
36
  _focus: { [$itemBg.variable]: hoverBg, bg: $itemBg.reference },
29
37
  _hover: { [$itemBg.variable]: hoverBg, bg: $itemBg.reference },
@@ -176,6 +176,12 @@ export declare const componentStyles: {
176
176
  borderColor: string;
177
177
  };
178
178
  item: {
179
+ minH: string;
180
+ px: string;
181
+ py: string;
182
+ mx: string;
183
+ fontSize: string;
184
+ borderRadius: string;
179
185
  bg: string;
180
186
  _focus: {
181
187
  [x: string]: string;
@@ -4,6 +4,8 @@ export declare const AA_NON_TEXT = 3;
4
4
  export declare const CHROME_BORDER = 1.5;
5
5
  export declare const CHROME_RULE = 1.31;
6
6
  export declare function ruleInk(hex: string, surfaces: readonly string[], target: number): string;
7
+ export declare const STACKED_STATE_STEP = 1.08;
8
+ export declare function tintStep(seed: string, backdrop: string, target: number): string;
7
9
  export declare function liftToContrast(hex: string, backdrop: string, target?: number): string;
8
10
  export declare function softenToContrast(hex: string, backdrop: string, target: number): string;
9
11
  export declare function withOutlineRung(palette: PaletteProps): PaletteProps;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CHROME_RULE = exports.CHROME_BORDER = exports.AA_NON_TEXT = exports.MIN_PERCEPTIBLE_STEP = void 0;
3
+ exports.STACKED_STATE_STEP = exports.CHROME_RULE = exports.CHROME_BORDER = exports.AA_NON_TEXT = exports.MIN_PERCEPTIBLE_STEP = void 0;
4
4
  exports.ruleInk = ruleInk;
5
+ exports.tintStep = tintStep;
5
6
  exports.liftToContrast = liftToContrast;
6
7
  exports.softenToContrast = softenToContrast;
7
8
  exports.withOutlineRung = withOutlineRung;
@@ -20,6 +21,18 @@ function ruleInk(hex, surfaces, target) {
20
21
  ? softenToContrast(hex, loudest, target)
21
22
  : hex;
22
23
  }
24
+ exports.STACKED_STATE_STEP = 1.08;
25
+ // NOTE: walks out from the backdrop, so it settles a too-loud seed down as readily as it lifts a faint one.
26
+ function tintStep(seed, backdrop, target) {
27
+ const { s } = (0, color_1.hexToHsl)(seed);
28
+ const away = (0, color_1.relativeLuminance)(backdrop) < 0.5 ? 1 : -1;
29
+ for (let l = (0, color_1.hexToHsl)(backdrop).l; l >= 0 && l <= 100; l += away) {
30
+ const next = (0, color_1.withLightness)(seed, l, s);
31
+ if ((0, color_1.contrastRatio)(next, backdrop) >= target)
32
+ return next;
33
+ }
34
+ return (0, color_1.withLightness)(seed, away > 0 ? 100 : 0, s);
35
+ }
23
36
  function liftToContrast(hex, backdrop, target = exports.AA_NON_TEXT) {
24
37
  if ((0, color_1.contrastRatio)(hex, backdrop) >= target)
25
38
  return hex;