@lotics/ui 2.6.1 → 3.1.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.
- package/package.json +2 -15
- package/src/react_native.d.ts +2 -2
- package/src/segmented_control.tsx +201 -0
- package/src/cell_date.tsx +0 -30
- package/src/cell_date_format.test.ts +0 -32
- package/src/cell_date_format.ts +0 -73
- package/src/cell_number.test.ts +0 -42
- package/src/cell_number.tsx +0 -25
- package/src/cell_number_format.ts +0 -42
- package/src/cell_select.tsx +0 -68
- package/src/cell_text.tsx +0 -45
- package/src/grid/data_grid.tsx +0 -2003
- package/src/grid/data_grid_columns.test.ts +0 -72
- package/src/grid/data_grid_columns.ts +0 -30
- package/src/grid/data_grid_context.ts +0 -119
- package/src/grid/dispatch_safely.ts +0 -39
- package/src/grid/engine.module.css +0 -114
- package/src/grid/engine.tsx +0 -1042
- package/src/grid/helpers.ts +0 -205
- package/src/grid/layout.test.ts +0 -515
- package/src/grid/layout.ts +0 -425
- package/src/grid/recycling.test.ts +0 -236
- package/src/grid/recycling.ts +0 -172
- package/src/grid/row_cell.module.css +0 -105
- package/src/grid/row_cell.tsx +0 -313
- package/src/grid/search_highlight.ts +0 -71
- package/src/grid/select_cell.tsx +0 -58
- package/src/grid/select_group_summary_cell.tsx +0 -76
- package/src/grid/select_header_cell.tsx +0 -32
- package/src/grid/skeleton_row.module.css +0 -34
- package/src/grid/skeleton_row.tsx +0 -20
- package/src/grid/use_grid_groups.ts +0 -311
- package/src/grid/use_scroll_to_cell.ts +0 -135
- package/src/grid/use_virtual_grid.ts +0 -383
- package/src/grid/visibility.test.ts +0 -208
- package/src/grid/visibility.ts +0 -77
- package/src/kanban/constants.ts +0 -18
- package/src/kanban/default_renderers.tsx +0 -160
- package/src/kanban/drag_preview.tsx +0 -157
- package/src/kanban/index.ts +0 -13
- package/src/kanban/insert_card_zone.tsx +0 -135
- package/src/kanban/kanban_board.tsx +0 -635
- package/src/kanban/kanban_card.tsx +0 -321
- package/src/kanban/kanban_column.tsx +0 -499
- package/src/kanban/placeholders.tsx +0 -54
- 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
|
-
}
|