@monolith-forensics/monolith-ui 1.5.1 → 1.5.2-dev.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/dist/Table/StateStorage.d.ts +4 -0
- package/dist/Table/StateStorage.js +13 -0
- package/dist/Table/Table.js +160 -12
- package/dist/Table/TableComponents.d.ts +10 -0
- package/dist/Table/TableComponents.js +49 -0
- package/dist/Table/TableDefaults.d.ts +7 -0
- package/dist/Table/TableDefaults.js +7 -0
- package/dist/Table/TableProvider.js +263 -71
- package/dist/Table/TableRow.js +14 -9
- package/dist/Table/types.d.ts +64 -0
- package/package.json +1 -1
|
@@ -11,6 +11,8 @@ type GetSearchStateFn = (key: string) => string;
|
|
|
11
11
|
type SetSearchStateFn = (key: string, value: string) => void;
|
|
12
12
|
type GetFilterStateFn = (key: string) => Query | undefined;
|
|
13
13
|
type SetFilterStateFn = (key: string, value?: Query | null) => void;
|
|
14
|
+
type GetExpandedKeysFn = (key: string) => string[] | undefined;
|
|
15
|
+
type SetExpandedKeysFn = (key: string, value: string[]) => void;
|
|
14
16
|
declare const StateStorage: {
|
|
15
17
|
getTableState: GetTableStateFn;
|
|
16
18
|
getColumnState: GetColumnStateFn;
|
|
@@ -23,5 +25,7 @@ declare const StateStorage: {
|
|
|
23
25
|
setSearchState: SetSearchStateFn;
|
|
24
26
|
getFilterState: GetFilterStateFn;
|
|
25
27
|
setFilterState: SetFilterStateFn;
|
|
28
|
+
getExpandedKeys: GetExpandedKeysFn;
|
|
29
|
+
setExpandedKeys: SetExpandedKeysFn;
|
|
26
30
|
};
|
|
27
31
|
export default StateStorage;
|
|
@@ -82,6 +82,17 @@ const setFilterState = (key, value) => {
|
|
|
82
82
|
const newState = Object.assign(Object.assign({}, previousState), { filterState: value });
|
|
83
83
|
set(key, newState);
|
|
84
84
|
};
|
|
85
|
+
const getExpandedKeys = (key) => {
|
|
86
|
+
const data = getTableState(key);
|
|
87
|
+
return data.expandedKeys;
|
|
88
|
+
};
|
|
89
|
+
const setExpandedKeys = (key, value) => {
|
|
90
|
+
// get previous state
|
|
91
|
+
const previousState = getTableState(key);
|
|
92
|
+
// merge previous state with new state
|
|
93
|
+
const newState = Object.assign(Object.assign({}, previousState), { expandedKeys: value });
|
|
94
|
+
set(key, newState);
|
|
95
|
+
};
|
|
85
96
|
const StateStorage = {
|
|
86
97
|
getTableState,
|
|
87
98
|
getColumnState,
|
|
@@ -94,5 +105,7 @@ const StateStorage = {
|
|
|
94
105
|
setSearchState,
|
|
95
106
|
getFilterState,
|
|
96
107
|
setFilterState,
|
|
108
|
+
getExpandedKeys,
|
|
109
|
+
setExpandedKeys,
|
|
97
110
|
};
|
|
98
111
|
export default StateStorage;
|
package/dist/Table/Table.js
CHANGED
|
@@ -22,6 +22,7 @@ import TableMenu from "./TableMenu";
|
|
|
22
22
|
import useTable from "./useTable";
|
|
23
23
|
import LoadingIndicator from "./LoadingIndicator";
|
|
24
24
|
import styled from "styled-components";
|
|
25
|
+
import TableDefaults from "./TableDefaults";
|
|
25
26
|
const StyledTableContainer = styled.div `
|
|
26
27
|
display: flex;
|
|
27
28
|
flex-direction: column;
|
|
@@ -40,8 +41,20 @@ const TableContent = ({ children, }) => {
|
|
|
40
41
|
}, ref: tableElement, "data-compact": compactState, children: [_jsx(LoadingIndicator, { visible: loading }), _jsx(TableHeader, { headerRowElm: headerRowElm }), visibleColumnCount === 0 && _jsx("div", { children: "No columns visible" }), virtualized === true ? (_jsx(VirtualizedRows, { tableDimensions: tableDimensions, targetElm: targetElm, listElm: listElm, rowHeight: rowHeight, headerRowHeight: headerRowHeight })) : (_jsx(StaticRows, { targetElm: targetElm, listElm: listElm }))] })] }));
|
|
41
42
|
};
|
|
42
43
|
export const Table = (_a) => {
|
|
43
|
-
var { data, columnProps, children } = _a, props = __rest(_a, ["data", "columnProps", "children"]) // pass through props straight to table context
|
|
44
|
+
var { data, columnProps, children, treeOptions } = _a, props = __rest(_a, ["data", "columnProps", "children", "treeOptions"]) // pass through props straight to table context
|
|
44
45
|
;
|
|
46
|
+
const resolvedTreeOptions = useMemo(() => {
|
|
47
|
+
var _a, _b, _c, _d, _e, _f;
|
|
48
|
+
return ({
|
|
49
|
+
enabled: (_a = treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.enabled) !== null && _a !== void 0 ? _a : false,
|
|
50
|
+
mode: (_b = treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.mode) !== null && _b !== void 0 ? _b : TableDefaults.tree.defaultMode,
|
|
51
|
+
childrenField: (_c = treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.childrenField) !== null && _c !== void 0 ? _c : TableDefaults.tree.defaultChildrenField,
|
|
52
|
+
parentIdField: (_d = treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.parentIdField) !== null && _d !== void 0 ? _d : TableDefaults.tree.defaultParentIdField,
|
|
53
|
+
idField: treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.idField,
|
|
54
|
+
indentPx: (_e = treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.indentPx) !== null && _e !== void 0 ? _e : TableDefaults.tree.indentPx,
|
|
55
|
+
autoExpandOnMatch: (_f = treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.autoExpandOnMatch) !== null && _f !== void 0 ? _f : true,
|
|
56
|
+
});
|
|
57
|
+
}, [treeOptions]);
|
|
45
58
|
const tableElement = useRef(null);
|
|
46
59
|
const targetElm = useRef(null);
|
|
47
60
|
const listElm = useRef(null);
|
|
@@ -113,18 +126,153 @@ export const Table = (_a) => {
|
|
|
113
126
|
// Uses a WeakMap so entries are automatically garbage collected
|
|
114
127
|
// when their corresponding row objects are no longer referenced.
|
|
115
128
|
const uuidCache = useRef(new WeakMap());
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// the same UUID across re-renders, preventing unnecessary DOM
|
|
119
|
-
// reconciliation. useMemo preserves referential stability of
|
|
120
|
-
// the output array when the data reference hasn't changed.
|
|
121
|
-
const __data = useMemo(() => data === null || data === void 0 ? void 0 : data.map((d, i) => {
|
|
122
|
-
let key = uuidCache.current.get(d);
|
|
129
|
+
const getOrAssignKey = (row) => {
|
|
130
|
+
let key = uuidCache.current.get(row);
|
|
123
131
|
if (!key) {
|
|
124
132
|
key = shortUUID.uuid();
|
|
125
|
-
uuidCache.current.set(
|
|
133
|
+
uuidCache.current.set(row, key);
|
|
134
|
+
}
|
|
135
|
+
return key;
|
|
136
|
+
};
|
|
137
|
+
// Augment each row with stable metadata. When tree mode is disabled,
|
|
138
|
+
// produces a flat 1:1 mapping with __key and __index. When enabled,
|
|
139
|
+
// walks the input (nested or flat) into a flat depth-first array
|
|
140
|
+
// and adds tree metadata (__level, __parentKey, __hasChildren, __childKeys).
|
|
141
|
+
const { rows: __data, treeMeta } = useMemo(() => {
|
|
142
|
+
const parentKeyMap = new Map();
|
|
143
|
+
const childrenKeyMap = new Map();
|
|
144
|
+
const expandableKeys = [];
|
|
145
|
+
if (!data) {
|
|
146
|
+
return {
|
|
147
|
+
rows: [],
|
|
148
|
+
treeMeta: { parentKeyMap, childrenKeyMap, expandableKeys },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (!resolvedTreeOptions.enabled) {
|
|
152
|
+
const rows = data.map((d, i) => (Object.assign(Object.assign({}, d), { __key: getOrAssignKey(d), __index: i })));
|
|
153
|
+
return {
|
|
154
|
+
rows,
|
|
155
|
+
treeMeta: { parentKeyMap, childrenKeyMap, expandableKeys },
|
|
156
|
+
};
|
|
126
157
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
158
|
+
const rows = [];
|
|
159
|
+
let counter = 0;
|
|
160
|
+
if (resolvedTreeOptions.mode === "nested") {
|
|
161
|
+
const childrenField = resolvedTreeOptions.childrenField;
|
|
162
|
+
const walk = (node, level, parentKey) => {
|
|
163
|
+
const key = getOrAssignKey(node);
|
|
164
|
+
const rawChildren = node === null || node === void 0 ? void 0 : node[childrenField];
|
|
165
|
+
const children = Array.isArray(rawChildren) ? rawChildren : [];
|
|
166
|
+
const childKeys = children.map((c) => getOrAssignKey(c));
|
|
167
|
+
// Strip the nested children field from the augmented copy to avoid
|
|
168
|
+
// memory bloat and prevent accidental iteration of the raw tree.
|
|
169
|
+
const _a = node, _b = childrenField, _stripped = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
170
|
+
const augmented = Object.assign(Object.assign({}, rest), { __key: key, __index: counter++, __level: level, __parentKey: parentKey, __hasChildren: children.length > 0, __childKeys: childKeys });
|
|
171
|
+
rows.push(augmented);
|
|
172
|
+
parentKeyMap.set(key, parentKey);
|
|
173
|
+
if (children.length > 0) {
|
|
174
|
+
childrenKeyMap.set(key, childKeys);
|
|
175
|
+
expandableKeys.push(key);
|
|
176
|
+
}
|
|
177
|
+
for (const child of children) {
|
|
178
|
+
walk(child, level + 1, key);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const rootKeys = [];
|
|
182
|
+
for (const root of data) {
|
|
183
|
+
rootKeys.push(getOrAssignKey(root));
|
|
184
|
+
walk(root, 0, undefined);
|
|
185
|
+
}
|
|
186
|
+
childrenKeyMap.set(undefined, rootKeys);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// flat mode
|
|
190
|
+
const idField = resolvedTreeOptions.idField || props.keyField;
|
|
191
|
+
if (!idField) {
|
|
192
|
+
throw new Error('Table treeOptions.mode="flat" requires either treeOptions.idField or keyField to be set so the table can resolve parent-child relationships against a stable id.');
|
|
193
|
+
}
|
|
194
|
+
const parentIdField = resolvedTreeOptions.parentIdField;
|
|
195
|
+
// Pass 1: assign __key, build idToKey
|
|
196
|
+
const idToKey = new Map();
|
|
197
|
+
const idToRow = new Map();
|
|
198
|
+
for (const d of data) {
|
|
199
|
+
const id = d === null || d === void 0 ? void 0 : d[idField];
|
|
200
|
+
if (id === undefined || id === null)
|
|
201
|
+
continue;
|
|
202
|
+
idToKey.set(id, getOrAssignKey(d));
|
|
203
|
+
idToRow.set(id, d);
|
|
204
|
+
}
|
|
205
|
+
// Pass 2: build parent->children id map. Roots = parentId null/undefined
|
|
206
|
+
// OR parentId points at a non-existent row.
|
|
207
|
+
const parentToChildIds = new Map();
|
|
208
|
+
const rootIds = [];
|
|
209
|
+
let warnedOrphan = false;
|
|
210
|
+
for (const d of data) {
|
|
211
|
+
const id = d === null || d === void 0 ? void 0 : d[idField];
|
|
212
|
+
if (id === undefined || id === null)
|
|
213
|
+
continue;
|
|
214
|
+
const parentId = d === null || d === void 0 ? void 0 : d[parentIdField];
|
|
215
|
+
if (parentId === undefined || parentId === null) {
|
|
216
|
+
rootIds.push(id);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (!idToRow.has(parentId)) {
|
|
220
|
+
if (!warnedOrphan) {
|
|
221
|
+
// eslint-disable-next-line no-console
|
|
222
|
+
console.warn(`Table tree (flat mode): row id "${id}" references missing parentId "${parentId}"; treating as root.`);
|
|
223
|
+
warnedOrphan = true;
|
|
224
|
+
}
|
|
225
|
+
rootIds.push(id);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (!parentToChildIds.has(parentId))
|
|
229
|
+
parentToChildIds.set(parentId, []);
|
|
230
|
+
parentToChildIds.get(parentId).push(id);
|
|
231
|
+
}
|
|
232
|
+
// Pass 3: DFS from roots with cycle detection
|
|
233
|
+
const visiting = new Set();
|
|
234
|
+
let warnedCycle = false;
|
|
235
|
+
const walkFlat = (id, level, parentKey) => {
|
|
236
|
+
if (visiting.has(id)) {
|
|
237
|
+
if (!warnedCycle) {
|
|
238
|
+
// eslint-disable-next-line no-console
|
|
239
|
+
console.warn(`Table tree (flat mode): cycle detected at row id "${id}"; truncating subtree.`);
|
|
240
|
+
warnedCycle = true;
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
visiting.add(id);
|
|
245
|
+
const node = idToRow.get(id);
|
|
246
|
+
const key = idToKey.get(id);
|
|
247
|
+
const childIds = parentToChildIds.get(id) || [];
|
|
248
|
+
const childKeys = childIds
|
|
249
|
+
.map((cid) => idToKey.get(cid))
|
|
250
|
+
.filter((k) => !!k);
|
|
251
|
+
const augmented = Object.assign(Object.assign({}, node), { __key: key, __index: counter++, __level: level, __parentKey: parentKey, __hasChildren: childIds.length > 0, __childKeys: childKeys });
|
|
252
|
+
rows.push(augmented);
|
|
253
|
+
parentKeyMap.set(key, parentKey);
|
|
254
|
+
if (childIds.length > 0) {
|
|
255
|
+
childrenKeyMap.set(key, childKeys);
|
|
256
|
+
expandableKeys.push(key);
|
|
257
|
+
}
|
|
258
|
+
for (const cid of childIds) {
|
|
259
|
+
walkFlat(cid, level + 1, key);
|
|
260
|
+
}
|
|
261
|
+
visiting.delete(id);
|
|
262
|
+
};
|
|
263
|
+
const rootKeys = [];
|
|
264
|
+
for (const id of rootIds) {
|
|
265
|
+
const k = idToKey.get(id);
|
|
266
|
+
if (k)
|
|
267
|
+
rootKeys.push(k);
|
|
268
|
+
walkFlat(id, 0, undefined);
|
|
269
|
+
}
|
|
270
|
+
childrenKeyMap.set(undefined, rootKeys);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
rows,
|
|
274
|
+
treeMeta: { parentKeyMap, childrenKeyMap, expandableKeys },
|
|
275
|
+
};
|
|
276
|
+
}, [data, resolvedTreeOptions, props.keyField]);
|
|
277
|
+
return (_jsx(TableProvider, Object.assign({ columns: columnProps, data: __data, tableElement: tableElement, headerRowElm: headerRowElm, tableDimensions: tableDimensions, targetElm: targetElm, listElm: listElm, treeOptions: resolvedTreeOptions, treeMeta: treeMeta }, props, { children: _jsx(TableContent, { children: children }) })));
|
|
130
278
|
};
|
|
@@ -6,6 +6,16 @@ export declare const THR: import("styled-components/dist/types").IStyledComponen
|
|
|
6
6
|
export declare const TD: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, TDProps>> & string;
|
|
7
7
|
export declare const TH: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof TDProps> & TDProps, never>> & string;
|
|
8
8
|
export declare const InnerCellContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
9
|
+
export declare const TreeCellWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
10
|
+
export declare const TreeIndentSpacer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
11
|
+
$level: number;
|
|
12
|
+
$indentPx: number;
|
|
13
|
+
}>> & string;
|
|
14
|
+
export declare const TreeChevronButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, {
|
|
15
|
+
$expanded: boolean;
|
|
16
|
+
}>> & string;
|
|
17
|
+
export declare const TreeChevronPlaceholder: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
18
|
+
export declare const TreeCellContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
9
19
|
export declare const TableViewPort: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
10
20
|
export declare const TableListElement: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
11
21
|
export declare const TableWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
@@ -115,6 +115,55 @@ export const InnerCellContent = styled.div `
|
|
|
115
115
|
overflow: hidden;
|
|
116
116
|
text-overflow: ellipsis;
|
|
117
117
|
`;
|
|
118
|
+
export const TreeCellWrapper = styled.div `
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-direction: row;
|
|
121
|
+
align-items: center;
|
|
122
|
+
flex: 1 1 auto;
|
|
123
|
+
min-width: 0;
|
|
124
|
+
gap: 4px;
|
|
125
|
+
`;
|
|
126
|
+
export const TreeIndentSpacer = styled.div `
|
|
127
|
+
flex: 0 0 auto;
|
|
128
|
+
width: ${({ $level, $indentPx }) => $level * $indentPx}px;
|
|
129
|
+
`;
|
|
130
|
+
export const TreeChevronButton = styled.button `
|
|
131
|
+
flex: 0 0 auto;
|
|
132
|
+
width: 16px;
|
|
133
|
+
height: 16px;
|
|
134
|
+
padding: 0;
|
|
135
|
+
margin: 0;
|
|
136
|
+
display: inline-flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
background: transparent;
|
|
140
|
+
border: none;
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
color: ${({ theme }) => theme.palette.text.secondary};
|
|
143
|
+
transition: transform 120ms ease;
|
|
144
|
+
transform: rotate(${({ $expanded }) => ($expanded ? "90deg" : "0deg")});
|
|
145
|
+
|
|
146
|
+
&:hover {
|
|
147
|
+
color: ${({ theme }) => theme.palette.text.primary};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
&:focus-visible {
|
|
151
|
+
outline: 1px solid ${({ theme }) => theme.palette.primary.main};
|
|
152
|
+
outline-offset: 1px;
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
export const TreeChevronPlaceholder = styled.div `
|
|
156
|
+
flex: 0 0 auto;
|
|
157
|
+
width: 16px;
|
|
158
|
+
height: 16px;
|
|
159
|
+
`;
|
|
160
|
+
export const TreeCellContent = styled.div `
|
|
161
|
+
flex: 1 1 auto;
|
|
162
|
+
min-width: 0;
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
text-overflow: ellipsis;
|
|
165
|
+
white-space: nowrap;
|
|
166
|
+
`;
|
|
118
167
|
export const TableViewPort = styled.div `
|
|
119
168
|
display: flex;
|
|
120
169
|
flex-direction: column;
|
|
@@ -21,5 +21,12 @@ declare const TableDefaults: {
|
|
|
21
21
|
minWidth: number;
|
|
22
22
|
maxWidth: number;
|
|
23
23
|
};
|
|
24
|
+
tree: {
|
|
25
|
+
indentPx: number;
|
|
26
|
+
chevronWidth: number;
|
|
27
|
+
defaultMode: "nested";
|
|
28
|
+
defaultChildrenField: string;
|
|
29
|
+
defaultParentIdField: string;
|
|
30
|
+
};
|
|
24
31
|
};
|
|
25
32
|
export default TableDefaults;
|
|
@@ -21,5 +21,12 @@ const TableDefaults = {
|
|
|
21
21
|
minWidth: 35,
|
|
22
22
|
maxWidth: 35,
|
|
23
23
|
},
|
|
24
|
+
tree: {
|
|
25
|
+
indentPx: 16,
|
|
26
|
+
chevronWidth: 16,
|
|
27
|
+
defaultMode: "nested",
|
|
28
|
+
defaultChildrenField: "children",
|
|
29
|
+
defaultParentIdField: "parentId",
|
|
30
|
+
},
|
|
24
31
|
};
|
|
25
32
|
export default TableDefaults;
|
|
@@ -28,6 +28,27 @@ import { SelectionStatus } from "./enums";
|
|
|
28
28
|
import moment from "moment";
|
|
29
29
|
import { useControlled } from "../Utilities";
|
|
30
30
|
import { exportTableToExcel } from "./Utils";
|
|
31
|
+
const RESERVED_FIELDS = [
|
|
32
|
+
"__key",
|
|
33
|
+
"__index",
|
|
34
|
+
"__level",
|
|
35
|
+
"__parentKey",
|
|
36
|
+
"__hasChildren",
|
|
37
|
+
"__childKeys",
|
|
38
|
+
];
|
|
39
|
+
const EMPTY_TREE_META = {
|
|
40
|
+
parentKeyMap: new Map(),
|
|
41
|
+
childrenKeyMap: new Map(),
|
|
42
|
+
expandableKeys: [],
|
|
43
|
+
};
|
|
44
|
+
const DEFAULT_TREE_OPTIONS = {
|
|
45
|
+
enabled: false,
|
|
46
|
+
mode: "nested",
|
|
47
|
+
childrenField: "children",
|
|
48
|
+
parentIdField: "parentId",
|
|
49
|
+
indentPx: 16,
|
|
50
|
+
autoExpandOnMatch: true,
|
|
51
|
+
};
|
|
31
52
|
const calculateSelectionTotal = (selectionState, totalRecords, dataLength = 0) => {
|
|
32
53
|
if (!selectionState) {
|
|
33
54
|
return 0;
|
|
@@ -47,11 +68,21 @@ const calculateSelectionTotal = (selectionState, totalRecords, dataLength = 0) =
|
|
|
47
68
|
};
|
|
48
69
|
export const TableContext = createContext(null);
|
|
49
70
|
const TableProvider = (_a) => {
|
|
50
|
-
var { children, columns, data, keyField, tableInstanceRef, stateStorage, tableMenuOptions, manualSorting, manualFiltering, manualSearch, manualExport, height, maxHeight, minHeight, focusedRowId, enableColumnResize, enableSorting, compact, totalRecords, onColumnStateChange, onColumnReorder, onColumnHeaderClick, onSort, onRowUpdated, tableElement, headerRowElm, tableDimensions, targetElm, listElm, defaultSelectionState, selectionState, onSelectionChange, defaultFilterState, filterState, onFilterChange } = _a, props = __rest(_a, ["children", "columns", "data", "keyField", "tableInstanceRef", "stateStorage", "tableMenuOptions", "manualSorting", "manualFiltering", "manualSearch", "manualExport", "height", "maxHeight", "minHeight", "focusedRowId", "enableColumnResize", "enableSorting", "compact", "totalRecords", "onColumnStateChange", "onColumnReorder", "onColumnHeaderClick", "onSort", "onRowUpdated", "tableElement", "headerRowElm", "tableDimensions", "targetElm", "listElm", "defaultSelectionState", "selectionState", "onSelectionChange", "defaultFilterState", "filterState", "onFilterChange"]);
|
|
71
|
+
var { children, columns, data, keyField, tableInstanceRef, stateStorage, tableMenuOptions, manualSorting, manualFiltering, manualSearch, manualExport, height, maxHeight, minHeight, focusedRowId, enableColumnResize, enableSorting, compact, totalRecords, onColumnStateChange, onColumnReorder, onColumnHeaderClick, onSort, onRowUpdated, tableElement, headerRowElm, tableDimensions, targetElm, listElm, defaultSelectionState, selectionState, onSelectionChange, defaultFilterState, filterState, onFilterChange, treeOptions, treeMeta, defaultExpandedKeys, expandedKeys, onExpandedChange } = _a, props = __rest(_a, ["children", "columns", "data", "keyField", "tableInstanceRef", "stateStorage", "tableMenuOptions", "manualSorting", "manualFiltering", "manualSearch", "manualExport", "height", "maxHeight", "minHeight", "focusedRowId", "enableColumnResize", "enableSorting", "compact", "totalRecords", "onColumnStateChange", "onColumnReorder", "onColumnHeaderClick", "onSort", "onRowUpdated", "tableElement", "headerRowElm", "tableDimensions", "targetElm", "listElm", "defaultSelectionState", "selectionState", "onSelectionChange", "defaultFilterState", "filterState", "onFilterChange", "treeOptions", "treeMeta", "defaultExpandedKeys", "expandedKeys", "onExpandedChange"]);
|
|
72
|
+
const _treeOptions = treeOptions || DEFAULT_TREE_OPTIONS;
|
|
73
|
+
const _treeMeta = treeMeta || EMPTY_TREE_META;
|
|
74
|
+
if (_treeOptions.enabled) {
|
|
75
|
+
if (RESERVED_FIELDS.includes(_treeOptions.childrenField)) {
|
|
76
|
+
throw new Error(`treeOptions.childrenField cannot be a reserved internal field: "${_treeOptions.childrenField}".`);
|
|
77
|
+
}
|
|
78
|
+
if (RESERVED_FIELDS.includes(_treeOptions.parentIdField)) {
|
|
79
|
+
throw new Error(`treeOptions.parentIdField cannot be a reserved internal field: "${_treeOptions.parentIdField}".`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
51
82
|
const _columns = useMemo(() => columns
|
|
52
83
|
.map((child, index) => {
|
|
53
|
-
if (child.dataField
|
|
54
|
-
throw new Error(
|
|
84
|
+
if (RESERVED_FIELDS.includes(child.dataField)) {
|
|
85
|
+
throw new Error(`dataField cannot be a reserved internal field: "${child.dataField}". Reserved: ${RESERVED_FIELDS.join(", ")}`);
|
|
55
86
|
}
|
|
56
87
|
// check for duplicate dataFields
|
|
57
88
|
const dataFieldCount = columns.filter((col) => col.dataField === child.dataField).length;
|
|
@@ -70,7 +101,7 @@ const TableProvider = (_a) => {
|
|
|
70
101
|
? StateStorage.getTableState(stateStorage.key)
|
|
71
102
|
: undefined;
|
|
72
103
|
}, [stateStorage === null || stateStorage === void 0 ? void 0 : stateStorage.key]);
|
|
73
|
-
const { columnState: savedColumnState, selectionState: savedSelectionState, sortState: savedSortState, searchState: savedSearchState, filterState: savedFilterState, } = savedTableState || {};
|
|
104
|
+
const { columnState: savedColumnState, selectionState: savedSelectionState, sortState: savedSortState, searchState: savedSearchState, filterState: savedFilterState, expandedKeys: savedExpandedKeys, } = savedTableState || {};
|
|
74
105
|
const [compactState, setCompactState] = useState(compact || false);
|
|
75
106
|
const [columnState, setColumnState] = useState(syncColumnState(_columns, savedColumnState));
|
|
76
107
|
const [search, setSearch] = useState(savedSearchState || "");
|
|
@@ -80,6 +111,8 @@ const TableProvider = (_a) => {
|
|
|
80
111
|
rules: [],
|
|
81
112
|
});
|
|
82
113
|
const [sortState, setSortState] = useState(savedSortState);
|
|
114
|
+
const [_expandedKeys, _setExpandedKeys] = useControlled(expandedKeys, defaultExpandedKeys || savedExpandedKeys || []);
|
|
115
|
+
const expandedKeysSet = useMemo(() => new Set(_expandedKeys), [_expandedKeys]);
|
|
83
116
|
const [_selectionState, _setSelectionState] = useControlled(selectionState, defaultSelectionState || {
|
|
84
117
|
selectedRowKeys: (savedSelectionState === null || savedSelectionState === void 0 ? void 0 : savedSelectionState.selectedRowKeys) || [],
|
|
85
118
|
excludedRowKeys: (savedSelectionState === null || savedSelectionState === void 0 ? void 0 : savedSelectionState.excludedRowKeys) || [],
|
|
@@ -227,58 +260,99 @@ const TableProvider = (_a) => {
|
|
|
227
260
|
}
|
|
228
261
|
}
|
|
229
262
|
};
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
if (aIsEmpty || bIsEmpty) {
|
|
243
|
-
if (sortState.dir === "asc") {
|
|
244
|
-
return aIsEmpty ? -1 : 1;
|
|
245
|
-
}
|
|
246
|
-
if (sortState.dir === "desc") {
|
|
247
|
-
return aIsEmpty ? 1 : -1;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
263
|
+
const compareRows = (a, b, sortState) => {
|
|
264
|
+
if (sortState) {
|
|
265
|
+
const aValue = a[sortState.dataField];
|
|
266
|
+
const bValue = b[sortState.dataField];
|
|
267
|
+
const aIsEmpty = aValue === null || aValue === undefined;
|
|
268
|
+
const bIsEmpty = bValue === null || bValue === undefined;
|
|
269
|
+
// Treat empty values as the smallest values.
|
|
270
|
+
if (aIsEmpty && bIsEmpty) {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
if (aIsEmpty || bIsEmpty) {
|
|
250
274
|
if (sortState.dir === "asc") {
|
|
251
|
-
|
|
252
|
-
return -1;
|
|
253
|
-
}
|
|
254
|
-
if (aValue > bValue) {
|
|
255
|
-
return 1;
|
|
256
|
-
}
|
|
257
|
-
return 0;
|
|
275
|
+
return aIsEmpty ? -1 : 1;
|
|
258
276
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return -1;
|
|
262
|
-
}
|
|
263
|
-
if (aValue < bValue) {
|
|
264
|
-
return 1;
|
|
265
|
-
}
|
|
266
|
-
return 0;
|
|
277
|
+
if (sortState.dir === "desc") {
|
|
278
|
+
return aIsEmpty ? 1 : -1;
|
|
267
279
|
}
|
|
268
280
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
281
|
+
if (sortState.dir === "asc") {
|
|
282
|
+
if (aValue < bValue)
|
|
283
|
+
return -1;
|
|
284
|
+
if (aValue > bValue)
|
|
285
|
+
return 1;
|
|
286
|
+
return 0;
|
|
272
287
|
}
|
|
273
|
-
if (
|
|
274
|
-
|
|
288
|
+
else if (sortState.dir === "desc") {
|
|
289
|
+
if (aValue > bValue)
|
|
290
|
+
return -1;
|
|
291
|
+
if (aValue < bValue)
|
|
292
|
+
return 1;
|
|
293
|
+
return 0;
|
|
275
294
|
}
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
}
|
|
296
|
+
// sort by __index
|
|
297
|
+
if (a.__index < b.__index)
|
|
298
|
+
return -1;
|
|
299
|
+
if (a.__index > b.__index)
|
|
300
|
+
return 1;
|
|
301
|
+
return 0;
|
|
302
|
+
};
|
|
303
|
+
const sortData = (sortState) => {
|
|
304
|
+
if (!_treeOptions.enabled) {
|
|
305
|
+
// Flat data — sort in-place (preserves existing behavior).
|
|
306
|
+
return data.sort((a, b) => compareRows(a, b, sortState));
|
|
307
|
+
}
|
|
308
|
+
// Tree-aware sort: sort siblings only, then re-DFS to rebuild the
|
|
309
|
+
// flat order. Cross-parent sorting is never allowed in tree mode.
|
|
310
|
+
const byKey = new Map();
|
|
311
|
+
const childrenByParent = new Map();
|
|
312
|
+
for (const row of data) {
|
|
313
|
+
byKey.set(row.__key, row);
|
|
314
|
+
const pk = row.__parentKey;
|
|
315
|
+
if (!childrenByParent.has(pk))
|
|
316
|
+
childrenByParent.set(pk, []);
|
|
317
|
+
childrenByParent.get(pk).push(row);
|
|
318
|
+
}
|
|
319
|
+
for (const [, siblings] of childrenByParent) {
|
|
320
|
+
siblings.sort((a, b) => compareRows(a, b, sortState));
|
|
321
|
+
}
|
|
322
|
+
const result = [];
|
|
323
|
+
const walk = (parentKey) => {
|
|
324
|
+
const siblings = childrenByParent.get(parentKey);
|
|
325
|
+
if (!siblings)
|
|
326
|
+
return;
|
|
327
|
+
for (const row of siblings) {
|
|
328
|
+
result.push(row);
|
|
329
|
+
if (row.__hasChildren)
|
|
330
|
+
walk(row.__key);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
walk(undefined);
|
|
334
|
+
return result;
|
|
335
|
+
};
|
|
336
|
+
const collectAncestors = (rows) => {
|
|
337
|
+
const ancestorSet = new Set();
|
|
338
|
+
const byKey = new Map();
|
|
339
|
+
for (const r of data)
|
|
340
|
+
byKey.set(r.__key, r);
|
|
341
|
+
for (const row of rows) {
|
|
342
|
+
let parentKey = row.__parentKey;
|
|
343
|
+
while (parentKey) {
|
|
344
|
+
if (ancestorSet.has(parentKey))
|
|
345
|
+
break;
|
|
346
|
+
ancestorSet.add(parentKey);
|
|
347
|
+
const parent = byKey.get(parentKey);
|
|
348
|
+
parentKey = parent === null || parent === void 0 ? void 0 : parent.__parentKey;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return ancestorSet;
|
|
278
352
|
};
|
|
279
|
-
const searchData = (searchText) => {
|
|
353
|
+
const searchData = (rows, searchText) => {
|
|
280
354
|
const columnKeys = columnState.map((col) => col.dataField);
|
|
281
|
-
|
|
355
|
+
const matched = rows.filter((row) => {
|
|
282
356
|
return columnKeys.some((key) => {
|
|
283
357
|
if (typeof row[key] === "string") {
|
|
284
358
|
return row[key].toLowerCase().includes(searchText.toLowerCase());
|
|
@@ -286,6 +360,16 @@ const TableProvider = (_a) => {
|
|
|
286
360
|
return false;
|
|
287
361
|
});
|
|
288
362
|
});
|
|
363
|
+
if (!_treeOptions.enabled) {
|
|
364
|
+
return { rows: matched, ancestorKeys: new Set() };
|
|
365
|
+
}
|
|
366
|
+
const ancestorKeys = collectAncestors(matched);
|
|
367
|
+
const visibleSet = new Set(matched.map((r) => r.__key));
|
|
368
|
+
ancestorKeys.forEach((k) => visibleSet.add(k));
|
|
369
|
+
return {
|
|
370
|
+
rows: rows.filter((r) => visibleSet.has(r.__key)),
|
|
371
|
+
ancestorKeys,
|
|
372
|
+
};
|
|
289
373
|
};
|
|
290
374
|
const rowMatchesRule = (row, rule) => {
|
|
291
375
|
var _a, _b, _c, _d;
|
|
@@ -385,16 +469,26 @@ const TableProvider = (_a) => {
|
|
|
385
469
|
return true;
|
|
386
470
|
}
|
|
387
471
|
};
|
|
388
|
-
const filterData = (filter) => {
|
|
389
|
-
if (!
|
|
390
|
-
return [];
|
|
472
|
+
const filterData = (rows, filter) => {
|
|
473
|
+
if (!rows)
|
|
474
|
+
return { rows: [], ancestorKeys: new Set() };
|
|
391
475
|
const { combinator, rules } = filter;
|
|
392
|
-
if (!combinator || !rules)
|
|
393
|
-
return
|
|
394
|
-
if (combinator === "or") {
|
|
395
|
-
return data.filter((row) => rules.some((rule) => rowMatchesRule(row, rule)));
|
|
476
|
+
if (!combinator || !rules || rules.length === 0) {
|
|
477
|
+
return { rows, ancestorKeys: new Set() };
|
|
396
478
|
}
|
|
397
|
-
|
|
479
|
+
const matched = combinator === "or"
|
|
480
|
+
? rows.filter((row) => rules.some((rule) => rowMatchesRule(row, rule)))
|
|
481
|
+
: rows.filter((row) => rules.every((rule) => rowMatchesRule(row, rule)));
|
|
482
|
+
if (!_treeOptions.enabled) {
|
|
483
|
+
return { rows: matched, ancestorKeys: new Set() };
|
|
484
|
+
}
|
|
485
|
+
const ancestorKeys = collectAncestors(matched);
|
|
486
|
+
const visibleSet = new Set(matched.map((r) => r.__key));
|
|
487
|
+
ancestorKeys.forEach((k) => visibleSet.add(k));
|
|
488
|
+
return {
|
|
489
|
+
rows: rows.filter((r) => visibleSet.has(r.__key)),
|
|
490
|
+
ancestorKeys,
|
|
491
|
+
};
|
|
398
492
|
};
|
|
399
493
|
const toggleColumnVisibility = (dataField) => {
|
|
400
494
|
const newColumnState = columnState.map((col) => {
|
|
@@ -423,6 +517,47 @@ const TableProvider = (_a) => {
|
|
|
423
517
|
const key = !!keyField ? row[keyField] : row.__key;
|
|
424
518
|
return String(key);
|
|
425
519
|
};
|
|
520
|
+
const firstVisibleDataField = useMemo(() => { var _a; return (_a = columnState.find((c) => c.visible !== false)) === null || _a === void 0 ? void 0 : _a.dataField; }, [columnState]);
|
|
521
|
+
const persistExpandedKeys = (next) => {
|
|
522
|
+
if ((stateStorage === null || stateStorage === void 0 ? void 0 : stateStorage.enabled) && (stateStorage === null || stateStorage === void 0 ? void 0 : stateStorage.type) === "localStorage") {
|
|
523
|
+
StateStorage.setExpandedKeys(stateStorage.key, next);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const updateExpandedKeys = (next) => {
|
|
527
|
+
_setExpandedKeys(next);
|
|
528
|
+
onExpandedChange === null || onExpandedChange === void 0 ? void 0 : onExpandedChange(next);
|
|
529
|
+
persistExpandedKeys(next);
|
|
530
|
+
};
|
|
531
|
+
const expandRow = (row) => {
|
|
532
|
+
var _a;
|
|
533
|
+
const key = (_a = row === null || row === void 0 ? void 0 : row.__key) !== null && _a !== void 0 ? _a : getRowKey(row);
|
|
534
|
+
if (expandedKeysSet.has(key))
|
|
535
|
+
return;
|
|
536
|
+
updateExpandedKeys([..._expandedKeys, key]);
|
|
537
|
+
};
|
|
538
|
+
const collapseRow = (row) => {
|
|
539
|
+
var _a;
|
|
540
|
+
const key = (_a = row === null || row === void 0 ? void 0 : row.__key) !== null && _a !== void 0 ? _a : getRowKey(row);
|
|
541
|
+
if (!expandedKeysSet.has(key))
|
|
542
|
+
return;
|
|
543
|
+
updateExpandedKeys(_expandedKeys.filter((k) => k !== key));
|
|
544
|
+
};
|
|
545
|
+
const toggleRowExpanded = (row) => {
|
|
546
|
+
var _a;
|
|
547
|
+
const key = (_a = row === null || row === void 0 ? void 0 : row.__key) !== null && _a !== void 0 ? _a : getRowKey(row);
|
|
548
|
+
if (expandedKeysSet.has(key)) {
|
|
549
|
+
updateExpandedKeys(_expandedKeys.filter((k) => k !== key));
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
updateExpandedKeys([..._expandedKeys, key]);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const expandAllRows = () => {
|
|
556
|
+
updateExpandedKeys([..._treeMeta.expandableKeys]);
|
|
557
|
+
};
|
|
558
|
+
const collapseAllRows = () => {
|
|
559
|
+
updateExpandedKeys([]);
|
|
560
|
+
};
|
|
426
561
|
const selectRow = (row) => {
|
|
427
562
|
const key = getRowKey(row);
|
|
428
563
|
const newSelectionState = {
|
|
@@ -560,6 +695,61 @@ const TableProvider = (_a) => {
|
|
|
560
695
|
const key = getRowKey(row);
|
|
561
696
|
return focusedRowId === key;
|
|
562
697
|
};
|
|
698
|
+
const { _data, effectiveExpandedKeys } = useMemo(() => {
|
|
699
|
+
let processedData = data || [];
|
|
700
|
+
if (manualSorting !== true) {
|
|
701
|
+
processedData = sortData(sortState);
|
|
702
|
+
}
|
|
703
|
+
const autoExpandedSet = new Set();
|
|
704
|
+
if (manualFiltering !== true && _filterState) {
|
|
705
|
+
const result = filterData(processedData, _filterState);
|
|
706
|
+
processedData = result.rows;
|
|
707
|
+
if (_treeOptions.enabled && _treeOptions.autoExpandOnMatch) {
|
|
708
|
+
result.ancestorKeys.forEach((k) => autoExpandedSet.add(k));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (manualSearch !== true && search) {
|
|
712
|
+
const result = searchData(processedData, search);
|
|
713
|
+
processedData = result.rows;
|
|
714
|
+
if (_treeOptions.enabled && _treeOptions.autoExpandOnMatch) {
|
|
715
|
+
result.ancestorKeys.forEach((k) => autoExpandedSet.add(k));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// Compute the effective expanded set: user expansion ∪ auto-expanded ancestors.
|
|
719
|
+
// Never mutates user's _expandedKeys.
|
|
720
|
+
const effective = new Set(expandedKeysSet);
|
|
721
|
+
autoExpandedSet.forEach((k) => effective.add(k));
|
|
722
|
+
// Final pass: drop rows whose ancestors are not in the effective set.
|
|
723
|
+
if (_treeOptions.enabled) {
|
|
724
|
+
const byKey = new Map();
|
|
725
|
+
for (const r of processedData)
|
|
726
|
+
byKey.set(r.__key, r);
|
|
727
|
+
processedData = processedData.filter((row) => {
|
|
728
|
+
let pk = row.__parentKey;
|
|
729
|
+
while (pk) {
|
|
730
|
+
if (!effective.has(pk))
|
|
731
|
+
return false;
|
|
732
|
+
const parent = byKey.get(pk);
|
|
733
|
+
pk = parent === null || parent === void 0 ? void 0 : parent.__parentKey;
|
|
734
|
+
}
|
|
735
|
+
return true;
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
return { _data: processedData, effectiveExpandedKeys: effective };
|
|
739
|
+
}, [
|
|
740
|
+
data,
|
|
741
|
+
columnState,
|
|
742
|
+
search,
|
|
743
|
+
sortState,
|
|
744
|
+
_filterState,
|
|
745
|
+
expandedKeysSet,
|
|
746
|
+
_treeOptions,
|
|
747
|
+
]);
|
|
748
|
+
const isRowExpanded = (row) => {
|
|
749
|
+
var _a;
|
|
750
|
+
const key = (_a = row === null || row === void 0 ? void 0 : row.__key) !== null && _a !== void 0 ? _a : getRowKey(row);
|
|
751
|
+
return effectiveExpandedKeys.has(key);
|
|
752
|
+
};
|
|
563
753
|
if (tableInstanceRef) {
|
|
564
754
|
tableInstanceRef.current = {
|
|
565
755
|
columnState,
|
|
@@ -576,6 +766,13 @@ const TableProvider = (_a) => {
|
|
|
576
766
|
clearSelections,
|
|
577
767
|
runSearch,
|
|
578
768
|
clearSearch,
|
|
769
|
+
expandRow,
|
|
770
|
+
collapseRow,
|
|
771
|
+
toggleRowExpanded,
|
|
772
|
+
isRowExpanded,
|
|
773
|
+
expandAllRows,
|
|
774
|
+
collapseAllRows,
|
|
775
|
+
getExpandedRowKeys: () => [..._expandedKeys],
|
|
579
776
|
getTableState: () => {
|
|
580
777
|
return {
|
|
581
778
|
columnState,
|
|
@@ -587,24 +784,12 @@ const TableProvider = (_a) => {
|
|
|
587
784
|
},
|
|
588
785
|
sortState,
|
|
589
786
|
searchState: search,
|
|
590
|
-
_filterState,
|
|
787
|
+
filterState: _filterState,
|
|
788
|
+
expandedKeys: [..._expandedKeys],
|
|
591
789
|
};
|
|
592
790
|
},
|
|
593
791
|
};
|
|
594
792
|
}
|
|
595
|
-
const _data = useMemo(() => {
|
|
596
|
-
let processedData = data; // create a new array to avoid mutating the original data
|
|
597
|
-
if (manualSorting !== true) {
|
|
598
|
-
processedData = sortData(sortState);
|
|
599
|
-
}
|
|
600
|
-
if (manualFiltering !== true && _filterState) {
|
|
601
|
-
processedData = filterData(_filterState);
|
|
602
|
-
}
|
|
603
|
-
if (manualSearch !== true && search) {
|
|
604
|
-
processedData = searchData(search);
|
|
605
|
-
}
|
|
606
|
-
return processedData;
|
|
607
|
-
}, [data, columnState, search, sortState, _filterState]);
|
|
608
793
|
return (_jsx(TableContext.Provider, { value: Object.assign({ columnState,
|
|
609
794
|
setColumnState,
|
|
610
795
|
sortState, searchState: search, totalRecords,
|
|
@@ -631,6 +816,13 @@ const TableProvider = (_a) => {
|
|
|
631
816
|
stateStorage, tableHeight: height, tableMaxHeight: maxHeight, tableMinHeight: minHeight, compact, tableElement: tableElement, headerRowElm: headerRowElm, tableDimensions: tableDimensions, targetElm: targetElm, listElm: listElm, enableColumnResize,
|
|
632
817
|
onSelectionChange,
|
|
633
818
|
onColumnStateChange,
|
|
634
|
-
onColumnReorder, onRowUpdated: onRowUpdated || (() => { }), tableMenuOptions, data: _data
|
|
819
|
+
onColumnReorder, onRowUpdated: onRowUpdated || (() => { }), tableMenuOptions, data: _data, treeOptions: _treeOptions, firstVisibleDataField, expandedKeys: _expandedKeys, effectiveExpandedKeys,
|
|
820
|
+
isRowExpanded,
|
|
821
|
+
toggleRowExpanded,
|
|
822
|
+
expandRow,
|
|
823
|
+
collapseRow,
|
|
824
|
+
expandAllRows,
|
|
825
|
+
collapseAllRows,
|
|
826
|
+
onExpandedChange }, props), children: children }));
|
|
635
827
|
};
|
|
636
828
|
export default TableProvider;
|
package/dist/Table/TableRow.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Maximize2Icon } from "lucide-react";
|
|
2
|
+
import { ChevronRightIcon, Maximize2Icon } from "lucide-react";
|
|
3
3
|
import ColumnResizer from "./ColumnResizer";
|
|
4
|
-
import { InnerCellContent, TD, TR } from "./TableComponents";
|
|
4
|
+
import { InnerCellContent, TD, TR, TreeCellContent, TreeCellWrapper, TreeChevronButton, TreeChevronPlaceholder, TreeIndentSpacer, } from "./TableComponents";
|
|
5
5
|
import useTable from "./useTable";
|
|
6
6
|
import ActionCell from "./ActionCell";
|
|
7
7
|
import ActionButton from "./ActionButton";
|
|
8
8
|
import CheckBox from "../CheckBox";
|
|
9
9
|
import { LoadingCellIndicator } from "./LoadingCellIndicator";
|
|
10
10
|
const TableRow = ({ rowData, loading, rowStyle }) => {
|
|
11
|
-
const { columnState, enableActionButton, onActionButtonClick, actionButtonIcon: Icon, enableSelection, selectRow, deselectRow, isRowSelected, isRowFocused, onRowUpdated, } = useTable();
|
|
11
|
+
const { columnState, enableActionButton, onActionButtonClick, actionButtonIcon: Icon, enableSelection, selectRow, deselectRow, isRowSelected, isRowFocused, onRowUpdated, treeOptions, firstVisibleDataField, isRowExpanded, toggleRowExpanded, } = useTable();
|
|
12
12
|
const selected = isRowSelected(rowData);
|
|
13
13
|
const focused = isRowFocused(rowData);
|
|
14
14
|
const handleSelectionChange = (e) => {
|
|
15
15
|
e === true ? selectRow(rowData) : deselectRow(rowData);
|
|
16
16
|
};
|
|
17
17
|
return (_jsxs(TR, { className: "mfui-tr", style: rowStyle, "data-key": rowData.__key, "data-selected": selected, "data-focused": focused, children: [enableSelection && (_jsx(ActionCell, { className: `mfui-td column-select`, children: _jsx(InnerCellContent, { className: "mfui inner-cell-content row-action", children: _jsx(CheckBox, { className: `mfui-checkbox`, value: selected, onChange: (e) => handleSelectionChange(e) }) }) })), enableActionButton && (_jsx(ActionCell, { className: `mfui-td column-action`, children: _jsx(InnerCellContent, { className: "mfui inner-cell-content row-action", children: _jsx(ActionButton, { variant: "subtle", onClick: () => onActionButtonClick === null || onActionButtonClick === void 0 ? void 0 : onActionButtonClick(rowData), children: Icon ? _jsx(Icon, { size: 14 }) : _jsx(Maximize2Icon, { size: 14 }) }) }) })), columnState.map((column, index) => {
|
|
18
|
+
var _a;
|
|
18
19
|
if (column.visible === false)
|
|
19
20
|
return null;
|
|
20
21
|
if (loading) {
|
|
@@ -24,16 +25,20 @@ const TableRow = ({ rowData, loading, rowStyle }) => {
|
|
|
24
25
|
flex: column.width ? "0 0 auto" : "1",
|
|
25
26
|
}, children: _jsx(LoadingCellIndicator, {}) }, index));
|
|
26
27
|
}
|
|
28
|
+
const cellBody = column.render
|
|
29
|
+
? column.render({ rowData, onRowUpdated })
|
|
30
|
+
: rowData[column.dataField];
|
|
31
|
+
const isTreeColumn = (treeOptions === null || treeOptions === void 0 ? void 0 : treeOptions.enabled) === true &&
|
|
32
|
+
column.dataField === firstVisibleDataField;
|
|
33
|
+
const expanded = isTreeColumn ? isRowExpanded(rowData) : false;
|
|
27
34
|
return (_jsxs(TD, { className: `mfui-td column-${column.columnId}`, "data-field": column.dataField, style: {
|
|
28
35
|
width: column.width,
|
|
29
36
|
minWidth: column.minWidth,
|
|
30
37
|
flex: column.width ? "0 0 auto" : "1",
|
|
31
|
-
}, children: [(column === null || column === void 0 ? void 0 : column.enableResize) === true && _jsx(ColumnResizer, { column: column }), _jsx(InnerCellContent, { className: "mfui inner-cell-content", children:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
36
|
-
: rowData[column.dataField] })] }, index));
|
|
38
|
+
}, children: [(column === null || column === void 0 ? void 0 : column.enableResize) === true && _jsx(ColumnResizer, { column: column }), _jsx(InnerCellContent, { className: "mfui inner-cell-content", children: isTreeColumn ? (_jsxs(TreeCellWrapper, { children: [_jsx(TreeIndentSpacer, { "$level": (_a = rowData.__level) !== null && _a !== void 0 ? _a : 0, "$indentPx": treeOptions.indentPx }), rowData.__hasChildren ? (_jsx(TreeChevronButton, { type: "button", "$expanded": expanded, onClick: (e) => {
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
toggleRowExpanded(rowData);
|
|
41
|
+
}, "aria-label": expanded ? "Collapse row" : "Expand row", "aria-expanded": expanded, children: _jsx(ChevronRightIcon, { size: 12 }) })) : (_jsx(TreeChevronPlaceholder, {})), _jsx(TreeCellContent, { children: cellBody })] })) : (cellBody) })] }, index));
|
|
37
42
|
})] }));
|
|
38
43
|
};
|
|
39
44
|
export default TableRow;
|
package/dist/Table/types.d.ts
CHANGED
|
@@ -10,6 +10,42 @@ export type StateStorage = {
|
|
|
10
10
|
type: "localStorage";
|
|
11
11
|
key: string;
|
|
12
12
|
};
|
|
13
|
+
export type TreeMode = "nested" | "flat";
|
|
14
|
+
export type TreeOptions = {
|
|
15
|
+
/** Enable tree-row behavior. Default: false */
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
/** Input shape. Default: "nested" */
|
|
18
|
+
mode?: TreeMode;
|
|
19
|
+
/** For mode="nested": field name that holds child array. Default: "children" */
|
|
20
|
+
childrenField?: string;
|
|
21
|
+
/** For mode="flat": field name that holds parent id. Default: "parentId" */
|
|
22
|
+
parentIdField?: string;
|
|
23
|
+
/** For mode="flat": field name that holds the row's stable id. Falls back to keyField. */
|
|
24
|
+
idField?: string;
|
|
25
|
+
/** Pixel width of one indentation level. Default: 16 */
|
|
26
|
+
indentPx?: number;
|
|
27
|
+
/** If a descendant matches search/filter, auto-expand its ancestors for the effective view. Default: true */
|
|
28
|
+
autoExpandOnMatch?: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type ResolvedTreeOptions = {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
mode: TreeMode;
|
|
33
|
+
childrenField: string;
|
|
34
|
+
parentIdField: string;
|
|
35
|
+
idField?: string;
|
|
36
|
+
indentPx: number;
|
|
37
|
+
autoExpandOnMatch: boolean;
|
|
38
|
+
};
|
|
39
|
+
export type ExpandedKeysState = string[];
|
|
40
|
+
export type OnExpandedChangeFn = (keys: string[]) => void;
|
|
41
|
+
export type TreeMeta = {
|
|
42
|
+
/** Map of row key -> parent row key (undefined for roots) */
|
|
43
|
+
parentKeyMap: Map<string, string | undefined>;
|
|
44
|
+
/** Map of parent key -> ordered list of direct child keys */
|
|
45
|
+
childrenKeyMap: Map<string | undefined, string[]>;
|
|
46
|
+
/** All row keys that have children (eligible for expansion) */
|
|
47
|
+
expandableKeys: string[];
|
|
48
|
+
};
|
|
13
49
|
export type RenderOptions = {
|
|
14
50
|
rowData: any;
|
|
15
51
|
onRowUpdated?: (context: {
|
|
@@ -55,6 +91,7 @@ export type TableState = {
|
|
|
55
91
|
sortState?: SortState;
|
|
56
92
|
filterState?: Query;
|
|
57
93
|
searchState?: string;
|
|
94
|
+
expandedKeys?: ExpandedKeysState;
|
|
58
95
|
};
|
|
59
96
|
export interface onResizeFinishedProps {
|
|
60
97
|
column: ColumnState;
|
|
@@ -102,6 +139,17 @@ export type TableContextType = {
|
|
|
102
139
|
data: any[];
|
|
103
140
|
totalRecords?: number;
|
|
104
141
|
keyField?: string;
|
|
142
|
+
treeOptions: ResolvedTreeOptions;
|
|
143
|
+
firstVisibleDataField?: string;
|
|
144
|
+
expandedKeys: string[];
|
|
145
|
+
effectiveExpandedKeys: Set<string>;
|
|
146
|
+
isRowExpanded: (row: any) => boolean;
|
|
147
|
+
toggleRowExpanded: (row: any) => void;
|
|
148
|
+
expandRow: (row: any) => void;
|
|
149
|
+
collapseRow: (row: any) => void;
|
|
150
|
+
expandAllRows: () => void;
|
|
151
|
+
collapseAllRows: () => void;
|
|
152
|
+
onExpandedChange?: OnExpandedChangeFn;
|
|
105
153
|
tableHeight?: number;
|
|
106
154
|
tableMaxHeight?: number;
|
|
107
155
|
tableMinHeight?: number;
|
|
@@ -213,6 +261,11 @@ export interface TableProviderProps {
|
|
|
213
261
|
defaultFilterState?: Query;
|
|
214
262
|
filterState?: Query;
|
|
215
263
|
onFilterChange?: (e: Query) => void;
|
|
264
|
+
treeOptions?: ResolvedTreeOptions;
|
|
265
|
+
defaultExpandedKeys?: ExpandedKeysState;
|
|
266
|
+
expandedKeys?: ExpandedKeysState;
|
|
267
|
+
onExpandedChange?: OnExpandedChangeFn;
|
|
268
|
+
treeMeta?: TreeMeta;
|
|
216
269
|
}
|
|
217
270
|
export interface TableRowProps {
|
|
218
271
|
rowData: {
|
|
@@ -334,6 +387,10 @@ export interface TableProps {
|
|
|
334
387
|
defaultFilterState?: Query;
|
|
335
388
|
filterState?: Query;
|
|
336
389
|
onFilterChange?: (e: Query) => void;
|
|
390
|
+
treeOptions?: TreeOptions;
|
|
391
|
+
defaultExpandedKeys?: ExpandedKeysState;
|
|
392
|
+
expandedKeys?: ExpandedKeysState;
|
|
393
|
+
onExpandedChange?: OnExpandedChangeFn;
|
|
337
394
|
}
|
|
338
395
|
export type TableInstance = {
|
|
339
396
|
columnState: ColumnState[];
|
|
@@ -354,4 +411,11 @@ export type TableInstance = {
|
|
|
354
411
|
clearSearch: () => void;
|
|
355
412
|
clearSelections: () => void;
|
|
356
413
|
getTableState: () => TableState;
|
|
414
|
+
expandRow: (row: any) => void;
|
|
415
|
+
collapseRow: (row: any) => void;
|
|
416
|
+
toggleRowExpanded: (row: any) => void;
|
|
417
|
+
isRowExpanded: (row: any) => boolean;
|
|
418
|
+
expandAllRows: () => void;
|
|
419
|
+
collapseAllRows: () => void;
|
|
420
|
+
getExpandedRowKeys: () => string[];
|
|
357
421
|
};
|