@lotics/ui 2.6.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +1 -15
  2. package/src/react_native.d.ts +2 -2
  3. package/src/cell_date.tsx +0 -30
  4. package/src/cell_date_format.test.ts +0 -32
  5. package/src/cell_date_format.ts +0 -73
  6. package/src/cell_number.test.ts +0 -42
  7. package/src/cell_number.tsx +0 -25
  8. package/src/cell_number_format.ts +0 -42
  9. package/src/cell_select.tsx +0 -68
  10. package/src/cell_text.tsx +0 -45
  11. package/src/grid/data_grid.tsx +0 -2003
  12. package/src/grid/data_grid_columns.test.ts +0 -72
  13. package/src/grid/data_grid_columns.ts +0 -30
  14. package/src/grid/data_grid_context.ts +0 -119
  15. package/src/grid/dispatch_safely.ts +0 -39
  16. package/src/grid/engine.module.css +0 -114
  17. package/src/grid/engine.tsx +0 -1042
  18. package/src/grid/helpers.ts +0 -205
  19. package/src/grid/layout.test.ts +0 -515
  20. package/src/grid/layout.ts +0 -425
  21. package/src/grid/recycling.test.ts +0 -236
  22. package/src/grid/recycling.ts +0 -172
  23. package/src/grid/row_cell.module.css +0 -105
  24. package/src/grid/row_cell.tsx +0 -313
  25. package/src/grid/search_highlight.ts +0 -71
  26. package/src/grid/select_cell.tsx +0 -58
  27. package/src/grid/select_group_summary_cell.tsx +0 -76
  28. package/src/grid/select_header_cell.tsx +0 -32
  29. package/src/grid/skeleton_row.module.css +0 -34
  30. package/src/grid/skeleton_row.tsx +0 -20
  31. package/src/grid/use_grid_groups.ts +0 -311
  32. package/src/grid/use_scroll_to_cell.ts +0 -135
  33. package/src/grid/use_virtual_grid.ts +0 -383
  34. package/src/grid/visibility.test.ts +0 -208
  35. package/src/grid/visibility.ts +0 -77
  36. package/src/kanban/constants.ts +0 -18
  37. package/src/kanban/default_renderers.tsx +0 -160
  38. package/src/kanban/drag_preview.tsx +0 -157
  39. package/src/kanban/index.ts +0 -13
  40. package/src/kanban/insert_card_zone.tsx +0 -135
  41. package/src/kanban/kanban_board.tsx +0 -635
  42. package/src/kanban/kanban_card.tsx +0 -321
  43. package/src/kanban/kanban_column.tsx +0 -499
  44. package/src/kanban/placeholders.tsx +0 -54
  45. package/src/kanban/types.ts +0 -116
@@ -1,76 +0,0 @@
1
- import { Checkbox } from "@lotics/ui/checkbox";
2
- import { Text } from "@lotics/ui/text";
3
- import { GroupPathKey, RowPathKey } from "@lotics/ui/grid/layout";
4
- import { useContext, useMemo, useCallback, memo } from "react";
5
- import { RowSelectionContext } from "./data_grid_context";
6
- import { Pressable, View } from "react-native";
7
- import { colors } from "@lotics/ui/colors";
8
-
9
- export const SelectGroupSummaryCell = memo(function SelectGroupSummaryCell(props: {
10
- groupKey: GroupPathKey;
11
- descendantRowKeys: RowPathKey[];
12
- }) {
13
- const { descendantRowKeys } = props;
14
- const context = useContext(RowSelectionContext);
15
- const selectedRows = context?.selectedRows;
16
- const onSelectedRowsChange = context?.onSelectedRowsChange;
17
-
18
- const selectedCount = useMemo(() => {
19
- if (!selectedRows || descendantRowKeys.length === 0) {
20
- return 0;
21
- }
22
- return descendantRowKeys.filter((rowPathKey) => selectedRows.has(rowPathKey)).length;
23
- }, [selectedRows, descendantRowKeys]);
24
-
25
- const isIndeterminate = selectedCount > 0 && selectedCount < descendantRowKeys.length;
26
- const isAllSelected = selectedCount === descendantRowKeys.length && descendantRowKeys.length > 0;
27
-
28
- const handlePress = useCallback(() => {
29
- if (!onSelectedRowsChange) {
30
- return;
31
- }
32
-
33
- const newSelectedRows = new Set(selectedRows ?? []);
34
-
35
- if (isAllSelected) {
36
- descendantRowKeys.forEach((rowPathKey) => {
37
- newSelectedRows.delete(rowPathKey);
38
- });
39
- } else {
40
- descendantRowKeys.forEach((rowPathKey) => {
41
- newSelectedRows.add(rowPathKey);
42
- });
43
- }
44
-
45
- onSelectedRowsChange(newSelectedRows);
46
- }, [onSelectedRowsChange, selectedRows, descendantRowKeys, isAllSelected]);
47
-
48
- const recordCount = descendantRowKeys.length;
49
- const shouldShowCheckbox = isIndeterminate || isAllSelected;
50
-
51
- return (
52
- <Pressable
53
- onPress={handlePress}
54
- style={{
55
- paddingHorizontal: 6,
56
- alignItems: "center",
57
- flexDirection: "row",
58
- height: "100%",
59
- cursor: "pointer",
60
- backgroundColor: colors.background,
61
- }}
62
- >
63
- {({ hovered }) =>
64
- hovered || shouldShowCheckbox ? (
65
- <Checkbox checked={isAllSelected} indeterminate={isIndeterminate} />
66
- ) : (
67
- <View style={{ width: 24, alignItems: "center" }}>
68
- <Text color="zinc-500" size="xs" userSelect="none">
69
- {recordCount}
70
- </Text>
71
- </View>
72
- )
73
- }
74
- </Pressable>
75
- );
76
- });
@@ -1,32 +0,0 @@
1
- import { CheckboxInput } from "@lotics/ui/checkbox_input";
2
- import { useHeaderRowSelection } from "./data_grid_context";
3
- import { memo } from "react";
4
- import { View } from "react-native";
5
-
6
- export const SelectHeaderCell = memo(function SelectHeaderCell() {
7
- const { isIndeterminate, isRowSelected, onRowSelectionChange } = useHeaderRowSelection();
8
-
9
- return (
10
- <View
11
- style={{
12
- paddingHorizontal: 6,
13
- height: 40,
14
- flexDirection: "row",
15
- gap: 4,
16
- alignItems: "center",
17
- }}
18
- >
19
- <CheckboxInput
20
- accessibilityLabel="Select all rows"
21
- testID="table-header-checkbox"
22
- indeterminate={isIndeterminate}
23
- checked={isRowSelected}
24
- onChange={(checked) => {
25
- onRowSelectionChange({
26
- checked: isIndeterminate ? false : checked,
27
- });
28
- }}
29
- />
30
- </View>
31
- );
32
- });
@@ -1,34 +0,0 @@
1
- .skeleton_row {
2
- position: absolute;
3
- top: 0;
4
- left: 0;
5
- display: flex;
6
- align-items: center;
7
- will-change: transform;
8
- content-visibility: auto;
9
- }
10
-
11
- .skeleton_row::before {
12
- content: "";
13
- width: 100%;
14
- height: 28px;
15
- border-radius: 4px;
16
- background-color: rgba(244, 244, 245, 1); /* zinc-100 */
17
- background-image: linear-gradient(
18
- 90deg,
19
- transparent 25%,
20
- rgba(255, 255, 255, 0.6) 50%,
21
- transparent 75%
22
- );
23
- background-size: 200% 100%;
24
- animation: shimmer 1.5s infinite linear;
25
- }
26
-
27
- @keyframes shimmer {
28
- 0% {
29
- background-position: 200% 0;
30
- }
31
- 100% {
32
- background-position: -200% 0;
33
- }
34
- }
@@ -1,20 +0,0 @@
1
- import { memo } from "react";
2
- import styles from "./skeleton_row.module.css";
3
-
4
- interface SkeletonRowProps {
5
- y: number;
6
- height: number;
7
- width: number;
8
- }
9
-
10
- export const SkeletonRow = memo(function SkeletonRow(props: SkeletonRowProps) {
11
- const { y, height, width } = props;
12
-
13
- return (
14
- <div
15
- className={styles.skeleton_row}
16
- data-testid="skeleton-row"
17
- style={{ height, width, transform: `translateY(${y}px)` }}
18
- />
19
- );
20
- });
@@ -1,311 +0,0 @@
1
- import { useMemo, useRef } from "react";
2
- import type {
3
- GridGroup,
4
- RowPath,
5
- RowPathKey,
6
- GroupPath,
7
- GroupPathKey,
8
- } from "@lotics/ui/grid/layout";
9
- import { rowPathToKey, groupPathToKey } from "@lotics/ui/grid/layout";
10
- import type { DataGridGroup } from "./data_grid";
11
- import { RowId } from "./data_grid_context";
12
-
13
- /**
14
- * Deep equality used to preserve referential identity across renders.
15
- * Vendored from @lotics/shared/utils so the grid module can be published
16
- * without depending on the workspace-private package.
17
- */
18
- function isEqual(a: unknown, b: unknown): boolean {
19
- if (a === b) return true;
20
- if (a == null || b == null) return a === b;
21
- if (typeof a !== typeof b) return false;
22
- if (Array.isArray(a) && Array.isArray(b)) {
23
- if (a.length !== b.length) return false;
24
- return a.every((item, index) => isEqual(item, b[index]));
25
- }
26
- if (typeof a === "object" && typeof b === "object") {
27
- const keysA = Object.keys(a as object);
28
- const keysB = Object.keys(b as object);
29
- if (keysA.length !== keysB.length) return false;
30
- return keysA.every((key) =>
31
- isEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),
32
- );
33
- }
34
- return false;
35
- }
36
-
37
- /**
38
- * Group metadata stored in groupDataMap.
39
- */
40
- export interface GroupData<TRow = unknown> {
41
- value: unknown;
42
- level: number;
43
- columnKey: string;
44
- rows: TRow[];
45
- }
46
-
47
- /**
48
- * Cached descendant data for efficient group operations.
49
- */
50
- export interface DescendantData<TRow = unknown> {
51
- /** Row path keys of all descendant rows */
52
- rowPathKeys: RowPathKey[];
53
- rows: TRow[];
54
- }
55
-
56
- interface UseGridGroupsParams<TRow> {
57
- /** Pre-grouped data structure containing all rows. */
58
- groups: DataGridGroup<TRow>[];
59
- /** Returns the unique data identifier for a row. Used to track row identity across updates. */
60
- rowIdGetter: (row: TRow) => RowId;
61
- }
62
-
63
- /**
64
- * Checks if a groups array represents a flat (ungrouped) structure.
65
- * A flat structure has a single group with columnKey "" (empty string).
66
- */
67
- function isFlatGroups<TRow>(groups: DataGridGroup<TRow>[]): boolean {
68
- return groups.length === 1 && groups[0].columnKey === "";
69
- }
70
-
71
- /**
72
- * Hook that manages grid grouping logic.
73
- * Accepts pre-grouped data from parent and builds mappings for efficient rendering.
74
- */
75
- export function useGridGroups<TRow>(params: UseGridGroupsParams<TRow>) {
76
- const { groups, rowIdGetter } = params;
77
-
78
- // Track previous values for identity preservation
79
- const prevRowsByKeyRef = useRef<Map<string, TRow>>(new Map());
80
- const prevRowPathKeysRef = useRef<Set<RowPathKey>>(new Set());
81
- const prevGridGroupsRef = useRef<GridGroup[]>([]);
82
- const prevRowDataMapRef = useRef<Map<RowPathKey, TRow>>(new Map());
83
- const prevGroupDataMapRef = useRef<Map<GroupPathKey, GroupData<TRow>>>(new Map());
84
- const prevDescendantCacheRef = useRef<Map<GroupPathKey, DescendantData<TRow>>>(new Map());
85
-
86
- // Convert pre-grouped structure to GridGroup format
87
- const gridGroups: GridGroup[] = useMemo(() => {
88
- let newGridGroups: GridGroup[];
89
-
90
- if (isFlatGroups(groups)) {
91
- // Flat structure - single row_group
92
- newGridGroups = [
93
- {
94
- type: "row_group" as const,
95
- rowCount: groups[0].rows?.length ?? 0,
96
- },
97
- ];
98
- } else {
99
- // Convert grouped structure to GridGroup format
100
- const convertGroups = (gs: DataGridGroup<TRow>[]): GridGroup[] => {
101
- return gs.map((group) => {
102
- if (group.children && group.children.length > 0) {
103
- return {
104
- type: "group" as const,
105
- children: convertGroups(group.children),
106
- };
107
- } else {
108
- return {
109
- type: "row_group" as const,
110
- rowCount: group.rows?.length ?? 0,
111
- };
112
- }
113
- });
114
- };
115
- newGridGroups = convertGroups(groups);
116
- }
117
-
118
- // Preserve identity if structure unchanged
119
- const prevGridGroups = prevGridGroupsRef.current;
120
- if (isEqual(prevGridGroups, newGridGroups)) {
121
- return prevGridGroups;
122
- }
123
- prevGridGroupsRef.current = newGridGroups;
124
- return newGridGroups;
125
- }, [groups]);
126
-
127
- // Build all data structures in single traversal
128
- const { groupDataMap, rowDataMap, allRowPathKeys, descendantCache } = useMemo(() => {
129
- const groupMap = new Map<GroupPathKey, GroupData<TRow>>();
130
- const rowMap = new Map<RowPathKey, TRow>();
131
- const rowPathKeySet = new Set<RowPathKey>();
132
- const descendantMap = new Map<GroupPathKey, DescendantData<TRow>>();
133
- const prevRowsByKey = prevRowsByKeyRef.current;
134
- const newRowsByKey = new Map<string, TRow>();
135
-
136
- // Helper to get row with preserved identity if unchanged
137
- const getStableRow = (row: TRow): TRow => {
138
- const dataKey = rowIdGetter(row);
139
- const prevRow = prevRowsByKey.get(dataKey);
140
- const stableRow = prevRow && isEqual(prevRow, row) ? prevRow : row;
141
- newRowsByKey.set(dataKey, stableRow);
142
- return stableRow;
143
- };
144
-
145
- if (isFlatGroups(groups)) {
146
- // Flat structure - process rows directly
147
- const rows = groups[0].rows ?? [];
148
- rows.forEach((row, index) => {
149
- const rowPath: RowPath = [0, index];
150
- const rowPathKey = rowPathToKey(rowPath);
151
- const stableRow = getStableRow(row);
152
- rowMap.set(rowPathKey, stableRow);
153
- rowPathKeySet.add(rowPathKey);
154
- });
155
-
156
- // Update ref with new rows map
157
- prevRowsByKeyRef.current = newRowsByKey;
158
-
159
- // Preserve allRowPathKeys identity if keys unchanged
160
- const prevRowPathKeys = prevRowPathKeysRef.current;
161
- const keysUnchanged =
162
- prevRowPathKeys.size === rowPathKeySet.size &&
163
- Array.from(rowPathKeySet).every((key) => prevRowPathKeys.has(key));
164
- const stableRowPathKeys = keysUnchanged ? prevRowPathKeys : rowPathKeySet;
165
- prevRowPathKeysRef.current = stableRowPathKeys;
166
-
167
- // Preserve rowDataMap identity if all entries unchanged
168
- const prevRowDataMap = prevRowDataMapRef.current;
169
- const mapUnchanged =
170
- prevRowDataMap.size === rowMap.size &&
171
- Array.from(rowMap.entries()).every(([key, value]) => prevRowDataMap.get(key) === value);
172
- const stableRowDataMap = mapUnchanged ? prevRowDataMap : rowMap;
173
- prevRowDataMapRef.current = stableRowDataMap;
174
-
175
- return {
176
- groupDataMap: groupMap,
177
- rowDataMap: stableRowDataMap,
178
- allRowPathKeys: stableRowPathKeys,
179
- descendantCache: descendantMap,
180
- };
181
- }
182
-
183
- // Process grouped structure
184
- const traverseGroups = (
185
- gs: DataGridGroup<TRow>[],
186
- parentPath: GroupPath,
187
- level: number,
188
- ): { rowPathKeys: RowPathKey[]; rows: TRow[] } => {
189
- const aggregatedRowPathKeys: RowPathKey[] = [];
190
- const aggregatedRows: TRow[] = [];
191
-
192
- for (let groupIndex = 0; groupIndex < gs.length; groupIndex++) {
193
- const group = gs[groupIndex];
194
- const currentGroupPath: GroupPath = [...parentPath, groupIndex];
195
- const groupPathKey = groupPathToKey(currentGroupPath);
196
-
197
- groupMap.set(groupPathKey, {
198
- value: group.value,
199
- level: level,
200
- columnKey: group.columnKey,
201
- rows: group.rows ?? [],
202
- });
203
-
204
- let descendantRowPathKeys: RowPathKey[];
205
- let descendantRows: TRow[];
206
-
207
- if (group.children && group.children.length > 0) {
208
- // Nested groups
209
- const childResult = traverseGroups(group.children, currentGroupPath, level + 1);
210
- descendantRowPathKeys = childResult.rowPathKeys;
211
- descendantRows = childResult.rows;
212
- } else {
213
- // Leaf group with rows
214
- descendantRowPathKeys = [];
215
- descendantRows = [];
216
- const groupRows = group.rows ?? [];
217
- groupRows.forEach((row, rowIndex) => {
218
- const rowPath: RowPath = [...currentGroupPath, rowIndex];
219
- const rowPathKey = rowPathToKey(rowPath);
220
- const stableRow = getStableRow(row);
221
- rowMap.set(rowPathKey, stableRow);
222
- rowPathKeySet.add(rowPathKey);
223
- descendantRowPathKeys.push(rowPathKey);
224
- descendantRows.push(stableRow);
225
- });
226
- }
227
-
228
- descendantMap.set(groupPathKey, {
229
- rowPathKeys: descendantRowPathKeys,
230
- rows: descendantRows,
231
- });
232
-
233
- aggregatedRowPathKeys.push(...descendantRowPathKeys);
234
- aggregatedRows.push(...descendantRows);
235
- }
236
-
237
- return { rowPathKeys: aggregatedRowPathKeys, rows: aggregatedRows };
238
- };
239
-
240
- traverseGroups(groups, [], 0);
241
-
242
- // Update ref with new rows map
243
- prevRowsByKeyRef.current = newRowsByKey;
244
-
245
- // Preserve allRowPathKeys identity if keys unchanged
246
- const prevRowPathKeys = prevRowPathKeysRef.current;
247
- const keysUnchanged =
248
- prevRowPathKeys.size === rowPathKeySet.size &&
249
- Array.from(rowPathKeySet).every((key) => prevRowPathKeys.has(key));
250
- const stableRowPathKeys = keysUnchanged ? prevRowPathKeys : rowPathKeySet;
251
- prevRowPathKeysRef.current = stableRowPathKeys;
252
-
253
- // Preserve rowDataMap identity if all entries unchanged
254
- const prevRowDataMap = prevRowDataMapRef.current;
255
- const mapUnchanged =
256
- prevRowDataMap.size === rowMap.size &&
257
- Array.from(rowMap.entries()).every(([key, value]) => prevRowDataMap.get(key) === value);
258
- const stableRowDataMap = mapUnchanged ? prevRowDataMap : rowMap;
259
- prevRowDataMapRef.current = stableRowDataMap;
260
-
261
- // Preserve groupDataMap identity if all entries unchanged
262
- const prevGroupDataMap = prevGroupDataMapRef.current;
263
- const groupMapUnchanged =
264
- prevGroupDataMap.size === groupMap.size &&
265
- Array.from(groupMap.entries()).every(([key, value]) => {
266
- const prevValue = prevGroupDataMap.get(key);
267
- if (!prevValue) return false;
268
- return (
269
- prevValue.value === value.value &&
270
- prevValue.level === value.level &&
271
- prevValue.columnKey === value.columnKey &&
272
- prevValue.rows.length === value.rows.length &&
273
- prevValue.rows.every((r, i) => r === value.rows[i])
274
- );
275
- });
276
- const stableGroupDataMap = groupMapUnchanged ? prevGroupDataMap : groupMap;
277
- prevGroupDataMapRef.current = stableGroupDataMap;
278
-
279
- // Preserve descendantCache identity if all entries unchanged
280
- const prevDescendantCache = prevDescendantCacheRef.current;
281
- const descendantCacheUnchanged =
282
- prevDescendantCache.size === descendantMap.size &&
283
- Array.from(descendantMap.entries()).every(([key, value]) => {
284
- const prevValue = prevDescendantCache.get(key);
285
- if (!prevValue) return false;
286
- return (
287
- prevValue.rowPathKeys.length === value.rowPathKeys.length &&
288
- prevValue.rowPathKeys.every((k, i) => k === value.rowPathKeys[i]) &&
289
- prevValue.rows.length === value.rows.length &&
290
- prevValue.rows.every((r, i) => r === value.rows[i])
291
- );
292
- });
293
- const stableDescendantCache = descendantCacheUnchanged ? prevDescendantCache : descendantMap;
294
- prevDescendantCacheRef.current = stableDescendantCache;
295
-
296
- return {
297
- groupDataMap: stableGroupDataMap,
298
- rowDataMap: stableRowDataMap,
299
- allRowPathKeys: stableRowPathKeys,
300
- descendantCache: stableDescendantCache,
301
- };
302
- }, [groups, rowIdGetter]);
303
-
304
- return {
305
- gridGroups,
306
- groupDataMap,
307
- rowDataMap,
308
- allRowPathKeys,
309
- descendantCache,
310
- };
311
- }
@@ -1,135 +0,0 @@
1
- import React, { useCallback, useMemo } from "react";
2
- import { isEmpty, max } from "./helpers";
3
- import {
4
- Column,
5
- DataRow,
6
- GridColumn,
7
- GridDataRow,
8
- GridRow,
9
- RowPathKey,
10
- rowPathToKey,
11
- } from "./layout";
12
-
13
- export interface ContentOffset {
14
- x: number;
15
- y: number;
16
- }
17
-
18
- export interface DataRowCell extends DataRow, Column {}
19
-
20
- interface UseScrollToCellOffsetProps {
21
- scrollViewHeight: number;
22
- scrollViewWidth: number;
23
- scrollX: number | React.RefObject<number>;
24
- scrollY: number | React.RefObject<number>;
25
- frozenColumnCount: number;
26
- columns: GridColumn[];
27
- rows: GridRow[];
28
- padding?: number;
29
- }
30
-
31
- export function useScrollToCellOffset(
32
- props: UseScrollToCellOffsetProps,
33
- ): (cell: Partial<DataRowCell>) => Partial<ContentOffset> {
34
- const {
35
- columns,
36
- rows,
37
- scrollViewHeight,
38
- scrollViewWidth,
39
- frozenColumnCount,
40
- scrollX,
41
- scrollY,
42
- padding = 40,
43
- } = props;
44
-
45
- const dataRowsCache = useMemo(() => {
46
- const cache = new Map<string, GridDataRow>();
47
-
48
- if (isEmpty(rows)) {
49
- return cache;
50
- }
51
-
52
- for (let i = 0; i < rows.length; i++) {
53
- const row = rows[i];
54
-
55
- if (row.type === "row") {
56
- cache.set(rowPathToKey(row.rowPath), row);
57
- }
58
- }
59
-
60
- return cache;
61
- }, [rows]);
62
-
63
- const getDataRow = useCallback(
64
- (rowPathKey?: RowPathKey): GridDataRow | undefined => {
65
- if (rowPathKey !== undefined) {
66
- return dataRowsCache.get(rowPathKey);
67
- }
68
- },
69
- [dataRowsCache],
70
- );
71
-
72
- const getScrollToRowOffset = useCallback(
73
- (row?: GridDataRow) => {
74
- if (row === undefined) {
75
- return;
76
- }
77
-
78
- const currentScrollY = typeof scrollY === "number" ? scrollY : scrollY.current;
79
- const { y, height } = row;
80
-
81
- const above = currentScrollY >= y;
82
- const below = y + height >= currentScrollY + scrollViewHeight;
83
-
84
- if (above) {
85
- return max(y - padding, 0);
86
- } else if (below) {
87
- return y + height - scrollViewHeight + padding;
88
- }
89
- },
90
- [scrollY, scrollViewHeight, padding],
91
- );
92
-
93
- const getScrollToColumnOffset = useCallback(
94
- (column?: number) => {
95
- if (column === undefined) {
96
- return;
97
- }
98
-
99
- if (column <= frozenColumnCount) {
100
- return 0;
101
- }
102
-
103
- const columnIndex = column - 1 - frozenColumnCount;
104
- if (columnIndex < 0 || columnIndex >= columns.length) {
105
- return;
106
- }
107
-
108
- const currentScrollX = typeof scrollX === "number" ? scrollX : scrollX.current;
109
- const { x, width } = columns[columnIndex];
110
-
111
- const left = currentScrollX >= x;
112
- const right = x + width >= currentScrollX + scrollViewWidth;
113
-
114
- if (left) {
115
- return max(x - padding, 0);
116
- } else if (right) {
117
- return x + width - scrollViewWidth + padding;
118
- }
119
- },
120
- [scrollX, columns, frozenColumnCount, scrollViewWidth, padding],
121
- );
122
-
123
- return useCallback(
124
- (cell: Partial<DataRowCell>): Partial<ContentOffset> => {
125
- const { rowPath, column } = cell;
126
- const rowPathKey = rowPath ? rowPathToKey(rowPath) : undefined;
127
-
128
- return {
129
- y: getScrollToRowOffset(getDataRow(rowPathKey)),
130
- x: getScrollToColumnOffset(column),
131
- };
132
- },
133
- [getScrollToRowOffset, getScrollToColumnOffset, getDataRow],
134
- );
135
- }