@homebound/beam 2.307.0 → 2.308.1
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/components/Table/GridTable.d.ts +1 -9
- package/dist/components/Table/GridTable.js +53 -149
- package/dist/components/Table/GridTableApi.js +1 -1
- package/dist/components/Table/utils/ColumnState.d.ts +1 -1
- package/dist/components/Table/utils/ColumnState.js +3 -3
- package/dist/components/Table/utils/RowState.d.ts +21 -3
- package/dist/components/Table/utils/RowState.js +82 -15
- package/dist/components/Table/utils/RowStates.d.ts +7 -4
- package/dist/components/Table/utils/RowStates.js +54 -42
- package/dist/components/Table/utils/RowStorage.js +2 -2
- package/dist/components/Table/utils/TableState.d.ts +10 -3
- package/dist/components/Table/utils/TableState.js +23 -6
- package/dist/components/Table/utils/sortRows.d.ts +2 -0
- package/dist/components/Table/utils/sortRows.js +12 -8
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MutableRefObject } from "react";
|
|
2
2
|
import { GridTableApi } from "./GridTableApi";
|
|
3
3
|
import { GridStyle, GridStyleDef, RowStyles } from "./TableStyles";
|
|
4
|
-
import { Direction, GridColumn,
|
|
4
|
+
import { Direction, GridColumn, GridTableXss, InfiniteScroll, Kinded, RenderAs } from "./types";
|
|
5
5
|
import { GridRowLookup } from "./utils/GridRowLookup";
|
|
6
6
|
import { Only } from "../../Css";
|
|
7
7
|
import { GridDataRow } from "./components/Row";
|
|
@@ -138,11 +138,3 @@ export interface GridTableProps<R extends Kinded, X> {
|
|
|
138
138
|
* https://docs.google.com/document/d/1DFnlkDubK4nG_GLf_hB8yp0flnSNt_3IBh5iOicuaFM/edit#heading=h.9m9cpwgeqfc9
|
|
139
139
|
*/
|
|
140
140
|
export declare function GridTable<R extends Kinded, X extends Only<GridTableXss, X> = any>(props: GridTableProps<R, X>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
141
|
-
/**
|
|
142
|
-
* Filters rows given a client-side text `filter.
|
|
143
|
-
*
|
|
144
|
-
* Ensures parent rows remain in the list if any children match the filter.
|
|
145
|
-
*
|
|
146
|
-
* We return a copy of `[Parent, [Child]]` tuples so that we don't modify the `GridDataRow.children`.
|
|
147
|
-
*/
|
|
148
|
-
export declare function filterRows<R extends Kinded>(api: GridTableApi<R>, columns: GridColumnWithId<R>[], rows: GridDataRow<R>[], filter: string | undefined): [string[], ParentChildrenTuple<R>[]];
|
|
@@ -26,10 +26,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.
|
|
29
|
+
exports.GridTable = exports.setGridTableDefaults = exports.setDefaultStyle = exports.setRunningInJest = void 0;
|
|
30
30
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
31
31
|
const memoize_one_1 = __importDefault(require("memoize-one"));
|
|
32
|
-
const mobx_1 = require("mobx");
|
|
33
32
|
const react_1 = __importStar(require("react"));
|
|
34
33
|
const react_virtuoso_1 = require("react-virtuoso");
|
|
35
34
|
const PresentationContext_1 = require("../PresentationContext");
|
|
@@ -38,7 +37,6 @@ const useSetupColumnSizes_1 = require("./hooks/useSetupColumnSizes");
|
|
|
38
37
|
const TableStyles_1 = require("./TableStyles");
|
|
39
38
|
const columns_1 = require("./utils/columns");
|
|
40
39
|
const GridRowLookup_1 = require("./utils/GridRowLookup");
|
|
41
|
-
const sortRows_1 = require("./utils/sortRows");
|
|
42
40
|
const TableState_1 = require("./utils/TableState");
|
|
43
41
|
const utils_1 = require("./utils/utils");
|
|
44
42
|
const Css_1 = require("../../Css");
|
|
@@ -117,16 +115,15 @@ function GridTable(props) {
|
|
|
117
115
|
// Initialize the sort state. This will only happen on the first render.
|
|
118
116
|
// Once the `TableState.sort` is defined, it will not re-initialize.
|
|
119
117
|
tableState.initSortState(props.sorting, columns);
|
|
120
|
-
const [sortOn, caseSensitive] = (0, hooks_1.useComputed)(() => {
|
|
121
|
-
const { sortConfig } = tableState;
|
|
122
|
-
return [sortConfig === null || sortConfig === void 0 ? void 0 : sortConfig.on, (sortConfig === null || sortConfig === void 0 ? void 0 : sortConfig.on) === "client" ? !!sortConfig.caseSensitive : false];
|
|
123
|
-
}, [tableState]);
|
|
124
118
|
(0, react_1.useEffect)(() => {
|
|
125
119
|
tableState.activeRowId = activeRowId;
|
|
126
120
|
}, [tableState, activeRowId]);
|
|
127
121
|
(0, react_1.useEffect)(() => {
|
|
128
122
|
tableState.activeCellId = activeCellId;
|
|
129
123
|
}, [tableState, activeCellId]);
|
|
124
|
+
(0, react_1.useEffect)(() => {
|
|
125
|
+
tableState.setSearch(filter);
|
|
126
|
+
}, [tableState, filter]);
|
|
130
127
|
// We track render count at the table level, which seems odd (we should be able to track this
|
|
131
128
|
// internally within each GridRow using a useRef), but we have suspicions that react-virtuoso
|
|
132
129
|
// (or us) is resetting component state more than necessary, so we track render counts from
|
|
@@ -135,111 +132,63 @@ function GridTable(props) {
|
|
|
135
132
|
// Our column sizes use either `w` or `expandedWidth`, so see which columns are currently expanded
|
|
136
133
|
const expandedColumnIds = (0, hooks_1.useComputed)(() => tableState.expandedColumnIds, [tableState]);
|
|
137
134
|
const columnSizes = (0, useSetupColumnSizes_1.useSetupColumnSizes)(style, columns, resizeTarget !== null && resizeTarget !== void 0 ? resizeTarget : resizeRef, expandedColumnIds);
|
|
138
|
-
// Make a single copy of our current collapsed state, so we'll have a single observer.
|
|
139
|
-
const collapsedIds = (0, hooks_1.useComputed)(() => tableState.collapsedIds, [tableState]);
|
|
140
|
-
const sortState = (0, hooks_1.useComputed)(() => (0, mobx_1.toJS)(tableState.sortState), [tableState]);
|
|
141
|
-
const maybeSorted = (0, react_1.useMemo)(() => {
|
|
142
|
-
if (sortOn === "client" && sortState) {
|
|
143
|
-
// If using client-side sort, the sortState use S = number
|
|
144
|
-
return (0, sortRows_1.sortRows)(columns, rows, sortState, caseSensitive);
|
|
145
|
-
}
|
|
146
|
-
return rows;
|
|
147
|
-
}, [columns, rows, sortOn, sortState, caseSensitive]);
|
|
148
|
-
const [keptGroupRow, keptDataRows] = (0, hooks_1.useComputed)(() => [tableState.keptRowGroup, tableState.keptRows], [tableState]);
|
|
149
|
-
// Sort the `keptSelectedDataRows` separately because the current sorting logic sorts within groups and these "kept" rows are now displayed in a flat list.
|
|
150
|
-
// It could also be the case that some of these rows are no longer in the `props.rows` list, and so wouldn't be sorted by the `maybeSorted` logic above.
|
|
151
|
-
const sortedKeptSelections = (0, react_1.useMemo)(() => {
|
|
152
|
-
if (sortOn === "client" && sortState && keptDataRows.length > 0) {
|
|
153
|
-
return (0, sortRows_1.sortRows)(columns, keptDataRows, sortState, caseSensitive);
|
|
154
|
-
}
|
|
155
|
-
return keptDataRows;
|
|
156
|
-
}, [columns, sortOn, sortState, caseSensitive, keptDataRows]);
|
|
157
135
|
// Flatten, hide-if-filtered, hide-if-collapsed, and component-ize the sorted rows.
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
const makeRowComponent = (row, level, isKeptSelectedRow = false, isLastKeptSelectionRow = false) => ((0, jsx_runtime_1.jsx)(Row_1.Row, { ...{
|
|
161
|
-
as,
|
|
162
|
-
columns,
|
|
163
|
-
row,
|
|
164
|
-
style,
|
|
165
|
-
rowStyles,
|
|
166
|
-
columnSizes,
|
|
167
|
-
level,
|
|
168
|
-
getCount,
|
|
169
|
-
api,
|
|
170
|
-
cellHighlight: "cellHighlight" in maybeStyle && maybeStyle.cellHighlight === true,
|
|
171
|
-
omitRowHover: "rowHover" in maybeStyle && maybeStyle.rowHover === false,
|
|
172
|
-
hasExpandableHeader,
|
|
173
|
-
isKeptSelectedRow,
|
|
174
|
-
isLastKeptSelectionRow,
|
|
175
|
-
} }, `${row.kind}-${row.id}`));
|
|
136
|
+
const [tableHeadRows, visibleDataRows, keptSelectedRows, tooManyClientSideRows] = (0, hooks_1.useComputed)(() => {
|
|
137
|
+
const columns = tableState.visibleColumns;
|
|
176
138
|
// Split out the header rows from the data rows so that we can put an `infoMessage` in between them (if needed).
|
|
177
139
|
const headerRows = [];
|
|
178
140
|
const expandableHeaderRows = [];
|
|
179
141
|
const totalsRows = [];
|
|
180
|
-
const visibleDataRows = [];
|
|
181
142
|
const keptSelectedRows = [];
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
143
|
+
let visibleDataRows = [];
|
|
144
|
+
const { visibleRows, keptRows } = tableState;
|
|
145
|
+
const hasExpandableHeader = visibleRows.some((rs) => rs.row.id === utils_1.EXPANDABLE_HEADER);
|
|
146
|
+
// Get the flat list or rows from the header down...
|
|
147
|
+
visibleRows.forEach((rs) => {
|
|
148
|
+
const row = rs.row;
|
|
149
|
+
const tuple = [
|
|
150
|
+
row,
|
|
151
|
+
(0, jsx_runtime_1.jsx)(Row_1.Row, { ...{
|
|
152
|
+
as,
|
|
153
|
+
columns,
|
|
154
|
+
row,
|
|
155
|
+
style,
|
|
156
|
+
rowStyles,
|
|
157
|
+
columnSizes,
|
|
158
|
+
level: rs.level,
|
|
159
|
+
getCount,
|
|
160
|
+
api,
|
|
161
|
+
cellHighlight: "cellHighlight" in maybeStyle && maybeStyle.cellHighlight === true,
|
|
162
|
+
omitRowHover: "rowHover" in maybeStyle && maybeStyle.rowHover === false,
|
|
163
|
+
hasExpandableHeader,
|
|
164
|
+
isKeptSelectedRow: rs.isKept,
|
|
165
|
+
isLastKeptSelectionRow: keptRows[keptRows.length - 1] === rs.row,
|
|
166
|
+
} }, `${row.kind}-${row.id}`),
|
|
167
|
+
];
|
|
168
|
+
if (row.kind === "header") {
|
|
169
|
+
headerRows.push(tuple);
|
|
170
|
+
}
|
|
171
|
+
else if (row.kind === "expandableHeader") {
|
|
172
|
+
expandableHeaderRows.push(tuple);
|
|
173
|
+
}
|
|
174
|
+
else if (row.kind === "totals") {
|
|
175
|
+
totalsRows.push(tuple);
|
|
176
|
+
}
|
|
177
|
+
else if (rs.isKept || row.kind === utils_1.KEPT_GROUP) {
|
|
178
|
+
keptSelectedRows.push(tuple);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
visibleDataRows.push(tuple);
|
|
216
182
|
}
|
|
183
|
+
});
|
|
184
|
+
// Once our header rows are created we can organize them in expected order.
|
|
185
|
+
const tableHeadRows = expandableHeaderRows.concat(headerRows).concat(totalsRows);
|
|
186
|
+
const tooManyClientSideRows = !!filterMaxRows && visibleDataRows.length > filterMaxRows;
|
|
187
|
+
if (tooManyClientSideRows) {
|
|
188
|
+
visibleDataRows = visibleDataRows.slice(0, filterMaxRows + keptSelectedRows.length);
|
|
217
189
|
}
|
|
218
|
-
return [
|
|
219
|
-
}, [
|
|
220
|
-
as,
|
|
221
|
-
api,
|
|
222
|
-
filter,
|
|
223
|
-
maybeSorted,
|
|
224
|
-
columns,
|
|
225
|
-
style,
|
|
226
|
-
rowStyles,
|
|
227
|
-
maybeStyle,
|
|
228
|
-
columnSizes,
|
|
229
|
-
collapsedIds,
|
|
230
|
-
getCount,
|
|
231
|
-
keptGroupRow,
|
|
232
|
-
sortedKeptSelections,
|
|
233
|
-
]);
|
|
234
|
-
// Once our header rows are created we can organize them in expected order.
|
|
235
|
-
const tableHeadRows = expandableHeaderRows.concat(headerRows).concat(totalsRows);
|
|
236
|
-
const tooManyClientSideRows = filterMaxRows && visibleDataRows.length > filterMaxRows;
|
|
237
|
-
if (tooManyClientSideRows) {
|
|
238
|
-
visibleDataRows = visibleDataRows.slice(0, filterMaxRows + keptSelectedRows.length);
|
|
239
|
-
}
|
|
240
|
-
(0, react_1.useEffect)(() => {
|
|
241
|
-
tableState.setMatchedRows(filteredRowIds);
|
|
242
|
-
}, [tableState, filteredRowIds]);
|
|
190
|
+
return [tableHeadRows, visibleDataRows, keptSelectedRows, tooManyClientSideRows];
|
|
191
|
+
}, [as, api, style, rowStyles, maybeStyle, columnSizes, getCount, filterMaxRows]);
|
|
243
192
|
// Push back to the caller a way to ask us where a row is.
|
|
244
193
|
const { rowLookup } = props;
|
|
245
194
|
if (rowLookup) {
|
|
@@ -417,48 +366,3 @@ const VirtualRoot = (0, memoize_one_1.default)((gs, _columns, id, xss) => {
|
|
|
417
366
|
}, "data-testid": id, children: children }));
|
|
418
367
|
});
|
|
419
368
|
});
|
|
420
|
-
/**
|
|
421
|
-
* Filters rows given a client-side text `filter.
|
|
422
|
-
*
|
|
423
|
-
* Ensures parent rows remain in the list if any children match the filter.
|
|
424
|
-
*
|
|
425
|
-
* We return a copy of `[Parent, [Child]]` tuples so that we don't modify the `GridDataRow.children`.
|
|
426
|
-
*/
|
|
427
|
-
function filterRows(api, columns, rows, filter) {
|
|
428
|
-
// Make a flat list of ids, in addition to the tuple tree
|
|
429
|
-
const filteredRowIds = [];
|
|
430
|
-
// Break up "foo bar" into `[foo, bar]` and a row must match both `foo` and `bar`
|
|
431
|
-
const filters = (filter && filter.split(/ +/)) || [];
|
|
432
|
-
// Make a functions to do recursion
|
|
433
|
-
function acceptAll(acc, row) {
|
|
434
|
-
var _a, _b;
|
|
435
|
-
filteredRowIds.push(row.id);
|
|
436
|
-
return acc.concat([[row, (_b = (_a = row.children) === null || _a === void 0 ? void 0 : _a.reduce(acceptAll, [])) !== null && _b !== void 0 ? _b : []]]);
|
|
437
|
-
}
|
|
438
|
-
function filterFn(acc, row) {
|
|
439
|
-
var _a, _b, _c, _d;
|
|
440
|
-
const matches = utils_1.reservedRowKinds.includes(row.kind) ||
|
|
441
|
-
filters.length === 0 ||
|
|
442
|
-
filters.every((f) => columns.map((c) => (0, utils_1.applyRowFn)(c, row, api, 0, false)).some((maybeContent) => (0, utils_1.matchesFilter)(maybeContent, f)));
|
|
443
|
-
if (matches) {
|
|
444
|
-
filteredRowIds.push(row.id);
|
|
445
|
-
// A matched parent means show all it's children
|
|
446
|
-
return acc.concat([[row, (_b = (_a = row.children) === null || _a === void 0 ? void 0 : _a.reduce(acceptAll, [])) !== null && _b !== void 0 ? _b : []]]);
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
// An unmatched parent but with matched children means show the parent
|
|
450
|
-
const matchedChildren = (_d = (_c = row.children) === null || _c === void 0 ? void 0 : _c.reduce(filterFn, [])) !== null && _d !== void 0 ? _d : [];
|
|
451
|
-
if (matchedChildren.length > 0 ||
|
|
452
|
-
typeof row.pin === "string" ||
|
|
453
|
-
(row.pin !== undefined && row.pin.filter !== true)) {
|
|
454
|
-
filteredRowIds.push(row.id);
|
|
455
|
-
return acc.concat([[row, matchedChildren]]);
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
return acc;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return [filteredRowIds, rows.reduce(filterFn, [])];
|
|
463
|
-
}
|
|
464
|
-
exports.filterRows = filterRows;
|
|
@@ -26,7 +26,7 @@ exports.useGridTableApi = useGridTableApi;
|
|
|
26
26
|
class GridTableApiImpl {
|
|
27
27
|
constructor() {
|
|
28
28
|
// This is public to GridTable but not exported outside of Beam
|
|
29
|
-
this.tableState = new TableState_1.TableState();
|
|
29
|
+
this.tableState = new TableState_1.TableState(this);
|
|
30
30
|
this.virtuosoRef = { current: null };
|
|
31
31
|
}
|
|
32
32
|
/** Called once by the GridTable when it takes ownership of this api instance. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ColumnState = void 0;
|
|
4
4
|
const mobx_1 = require("mobx");
|
|
5
|
-
const
|
|
5
|
+
const columns_1 = require("./columns");
|
|
6
6
|
const index_1 = require("../../../utils/index");
|
|
7
7
|
/**
|
|
8
8
|
* A reactive/observable wrapper around each GridColumn.
|
|
@@ -24,7 +24,7 @@ class ColumnState {
|
|
|
24
24
|
this.expanded = true;
|
|
25
25
|
this.doExpand();
|
|
26
26
|
}
|
|
27
|
-
(0, mobx_1.makeAutoObservable)(this, { column: mobx_1.observable.ref });
|
|
27
|
+
(0, mobx_1.makeAutoObservable)(this, { column: mobx_1.observable.ref }, { name: `ColumnState@${column.id}` });
|
|
28
28
|
}
|
|
29
29
|
setVisible(visible) {
|
|
30
30
|
const wasVisible = this.visible;
|
|
@@ -55,7 +55,7 @@ class ColumnState {
|
|
|
55
55
|
return;
|
|
56
56
|
if ((0, index_1.isFunction)(expandColumns)) {
|
|
57
57
|
const ecs = await expandColumns();
|
|
58
|
-
this.children = (0,
|
|
58
|
+
this.children = (0, columns_1.assignDefaultColumnIds)(ecs).map((ec) => this.states.addColumn(ec));
|
|
59
59
|
}
|
|
60
60
|
else if (expandColumns) {
|
|
61
61
|
this.children = expandColumns.map((ec) => this.states.addColumn(ec));
|
|
@@ -9,12 +9,11 @@ import { SelectedState } from "./TableState";
|
|
|
9
9
|
*/
|
|
10
10
|
export declare class RowState {
|
|
11
11
|
private states;
|
|
12
|
+
parent: RowState | undefined;
|
|
12
13
|
/** Our row, only ref observed, so we don't crawl into GraphQL fragments. */
|
|
13
14
|
row: GridDataRow<any>;
|
|
14
15
|
/** Our children row states, as of the latest `props.rows`, without any filtering applied. */
|
|
15
16
|
children: RowState[] | undefined;
|
|
16
|
-
/** Whether we match a client-side filter; true if no filter is in place. */
|
|
17
|
-
isMatched: boolean;
|
|
18
17
|
/** Whether we are *directly* selected. */
|
|
19
18
|
selected: boolean;
|
|
20
19
|
/** Whether we are collapsed. */
|
|
@@ -38,7 +37,14 @@ export declare class RowState {
|
|
|
38
37
|
* actively removed.
|
|
39
38
|
*/
|
|
40
39
|
removed: false | "soft" | "hard";
|
|
41
|
-
constructor(states: RowStates, row: GridDataRow<any>);
|
|
40
|
+
constructor(states: RowStates, parent: RowState | undefined, row: GridDataRow<any>);
|
|
41
|
+
/**
|
|
42
|
+
* Whether we match a client-side filter; true if no filter is in place.
|
|
43
|
+
*
|
|
44
|
+
* We should try and keep this based solely on "does/does not match the filter",
|
|
45
|
+
* and do any overrides for things like pinning/kept rows/etc. elsewhere.
|
|
46
|
+
*/
|
|
47
|
+
get isMatched(): boolean;
|
|
42
48
|
/**
|
|
43
49
|
* Whether we are effectively selected, for `GridTableApi.getSelectedRows`.
|
|
44
50
|
*
|
|
@@ -69,8 +75,13 @@ export declare class RowState {
|
|
|
69
75
|
toggleCollapsed(): void;
|
|
70
76
|
/** Whether this is a selected-but-filtered-out row that we should hoist to the top. */
|
|
71
77
|
get isKept(): boolean;
|
|
78
|
+
get level(): number;
|
|
72
79
|
private get inferSelectedState();
|
|
80
|
+
/** Returns this row and, if we're not collapsed, our children. */
|
|
81
|
+
get selfAndMaybeChildren(): RowState[];
|
|
73
82
|
private get visibleChildren();
|
|
83
|
+
/** The `visibleChildren`, but with the current sort config applied. */
|
|
84
|
+
private get visibleSortedChildren();
|
|
74
85
|
/**
|
|
75
86
|
* Returns whether this row should act like a parent.
|
|
76
87
|
*
|
|
@@ -83,4 +94,11 @@ export declare class RowState {
|
|
|
83
94
|
* they want the row to be selectable.
|
|
84
95
|
*/
|
|
85
96
|
private get isParent();
|
|
97
|
+
private get isPinned();
|
|
98
|
+
get isReservedKind(): boolean;
|
|
99
|
+
/** A dedicated method to "looking down" recursively, to avoid loops in `isMatched`. */
|
|
100
|
+
private get hasDirectlyMatchedChildren();
|
|
101
|
+
/** A dedicated method to "looking up" recursively, to avoid loops in `isMatched`. */
|
|
102
|
+
private get hasDirectlyMatchedParent();
|
|
103
|
+
private get isDirectlyMatched();
|
|
86
104
|
}
|
|
@@ -10,16 +10,12 @@ const utils_1 = require("./utils");
|
|
|
10
10
|
* that uses parent/children easier to write, i.e. selected-ness and collapsed-ness.
|
|
11
11
|
*/
|
|
12
12
|
class RowState {
|
|
13
|
-
|
|
14
|
-
// isDirectlyMatched = accept filters in the constructor and do match here
|
|
15
|
-
// isEffectiveMatched = isDirectlyMatched || hasMatchedChildren
|
|
16
|
-
constructor(states, row) {
|
|
13
|
+
constructor(states, parent, row) {
|
|
17
14
|
var _a;
|
|
18
15
|
this.states = states;
|
|
16
|
+
this.parent = parent;
|
|
19
17
|
/** Our children row states, as of the latest `props.rows`, without any filtering applied. */
|
|
20
18
|
this.children = undefined;
|
|
21
|
-
/** Whether we match a client-side filter; true if no filter is in place. */
|
|
22
|
-
this.isMatched = true;
|
|
23
19
|
/** Whether we are *directly* selected. */
|
|
24
20
|
this.selected = false;
|
|
25
21
|
/** Whether we are collapsed. */
|
|
@@ -46,7 +42,20 @@ class RowState {
|
|
|
46
42
|
this.row = row;
|
|
47
43
|
this.selected = !!row.initSelected;
|
|
48
44
|
this.collapsed = (_a = states.storage.wasCollapsed(row.id)) !== null && _a !== void 0 ? _a : !!row.initCollapsed;
|
|
49
|
-
(0, mobx_1.makeAutoObservable)(this, { row: mobx_1.observable.ref });
|
|
45
|
+
(0, mobx_1.makeAutoObservable)(this, { row: mobx_1.observable.ref }, { name: `RowState@${row.id}` });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether we match a client-side filter; true if no filter is in place.
|
|
49
|
+
*
|
|
50
|
+
* We should try and keep this based solely on "does/does not match the filter",
|
|
51
|
+
* and do any overrides for things like pinning/kept rows/etc. elsewhere.
|
|
52
|
+
*/
|
|
53
|
+
get isMatched() {
|
|
54
|
+
return (this.isDirectlyMatched ||
|
|
55
|
+
// A matched parent means show all it's children
|
|
56
|
+
this.hasDirectlyMatchedParent ||
|
|
57
|
+
// An unmatched parent but with matched children means show the parent
|
|
58
|
+
this.hasDirectlyMatchedChildren);
|
|
50
59
|
}
|
|
51
60
|
/**
|
|
52
61
|
* Whether we are effectively selected, for `GridTableApi.getSelectedRows`.
|
|
@@ -135,23 +144,47 @@ class RowState {
|
|
|
135
144
|
// - or it has (probably) been server-side filtered
|
|
136
145
|
return (this.selected &&
|
|
137
146
|
// Headers, totals, etc., do not need keeping
|
|
138
|
-
!
|
|
147
|
+
!this.isReservedKind &&
|
|
139
148
|
!this.isParent &&
|
|
140
149
|
(!this.isMatched || this.removed === "soft"));
|
|
141
150
|
}
|
|
151
|
+
get level() {
|
|
152
|
+
// Make the header level -1, so the top-level rows are level 0
|
|
153
|
+
return !this.parent ? -1 : this.parent.level + 1;
|
|
154
|
+
}
|
|
142
155
|
get inferSelectedState() {
|
|
143
156
|
return this.row.inferSelectedState !== false;
|
|
144
157
|
}
|
|
158
|
+
/** Returns this row and, if we're not collapsed, our children. */
|
|
159
|
+
get selfAndMaybeChildren() {
|
|
160
|
+
// The header always returns all children/top rows, even if collapsed
|
|
161
|
+
if (this.children && (!this.collapsed || this.row.kind === utils_1.HEADER)) {
|
|
162
|
+
return [this, ...this.visibleSortedChildren.flatMap((c) => c.selfAndMaybeChildren)];
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
return [this];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
145
168
|
get visibleChildren() {
|
|
146
169
|
var _a, _b;
|
|
147
170
|
// The keptGroup is special and its children are the dynamically kept rows
|
|
148
171
|
if (this.row.kind === utils_1.KEPT_GROUP)
|
|
149
172
|
return this.states.keptRows;
|
|
150
|
-
|
|
151
|
-
// rows
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
173
|
+
return ((_b = (_a = this.children) === null || _a === void 0 ? void 0 : _a.filter((rs) =>
|
|
174
|
+
// Reserved rows are always visible, even though they're not considered matched.
|
|
175
|
+
// ...except for the kept group: its `isMatched` will become true whenever it has
|
|
176
|
+
// any kept row children, as they will cause its hasDirectlyMatchedChildren to be true.
|
|
177
|
+
(rs.isReservedKind && rs.row.kind !== utils_1.KEPT_GROUP) || rs.isMatched || rs.isPinned)) !== null && _b !== void 0 ? _b : []);
|
|
178
|
+
}
|
|
179
|
+
/** The `visibleChildren`, but with the current sort config applied. */
|
|
180
|
+
get visibleSortedChildren() {
|
|
181
|
+
let rows = this.visibleChildren;
|
|
182
|
+
const { sortFn } = this.states.table;
|
|
183
|
+
// We need to make a copy for mobx to see the sort as a change, and also to not mutate
|
|
184
|
+
// the original/unsorted array if we need to revert to the original sort order.
|
|
185
|
+
if (sortFn)
|
|
186
|
+
rows = [...rows].sort(sortFn);
|
|
187
|
+
return rows;
|
|
155
188
|
}
|
|
156
189
|
/**
|
|
157
190
|
* Returns whether this row should act like a parent.
|
|
@@ -167,9 +200,43 @@ class RowState {
|
|
|
167
200
|
get isParent() {
|
|
168
201
|
return !!this.children && this.children.length > 0 && this.inferSelectedState;
|
|
169
202
|
}
|
|
170
|
-
|
|
203
|
+
get isPinned() {
|
|
204
|
+
return typeof this.row.pin === "string" || (!!this.row.pin && this.row.pin.filter !== true);
|
|
205
|
+
}
|
|
206
|
+
get isReservedKind() {
|
|
207
|
+
return utils_1.reservedRowKinds.includes(this.row.kind);
|
|
208
|
+
}
|
|
209
|
+
/** A dedicated method to "looking down" recursively, to avoid loops in `isMatched`. */
|
|
210
|
+
get hasDirectlyMatchedChildren() {
|
|
211
|
+
// The keptGroup is special and its children are the dynamically kept rows
|
|
212
|
+
if (this.row.kind === utils_1.KEPT_GROUP)
|
|
213
|
+
return this.states.keptRows.length > 0;
|
|
214
|
+
return !!this.children && this.children.some((c) => c.isDirectlyMatched || c.hasDirectlyMatchedChildren);
|
|
215
|
+
}
|
|
216
|
+
/** A dedicated method to "looking up" recursively, to avoid loops in `isMatched`. */
|
|
217
|
+
get hasDirectlyMatchedParent() {
|
|
218
|
+
return !!this.parent && (this.parent.isDirectlyMatched || this.parent.hasDirectlyMatchedParent);
|
|
219
|
+
}
|
|
220
|
+
get isDirectlyMatched() {
|
|
221
|
+
// Reserved rows like the header can never be directly matched, and treating them
|
|
222
|
+
// as matched currently throws off the header's select all/etc. behavior
|
|
223
|
+
if (this.isReservedKind)
|
|
224
|
+
return false;
|
|
225
|
+
// Ignore hard-deleted rows, i.e. from `api.deleteRows`; in theory any hard-deleted
|
|
226
|
+
// rows should be removed from `this.children` anyway, by a change to `props.rows`,
|
|
227
|
+
// but just in case the user calls _only_ `api.deleteRows`, and expects the row to
|
|
228
|
+
// go away, go ahead and filter them out here.
|
|
229
|
+
if (this.removed === "hard")
|
|
230
|
+
return false;
|
|
231
|
+
// Reacts to either search state or visibleColumns state changing
|
|
232
|
+
const { visibleColumns, api, search } = this.states.table;
|
|
233
|
+
return search.every((term) => visibleColumns
|
|
234
|
+
.map((c) => (0, utils_1.applyRowFn)(c, this.row, api, 0, false))
|
|
235
|
+
.some((maybeContent) => (0, utils_1.matchesFilter)(maybeContent, term)));
|
|
236
|
+
}
|
|
237
|
+
/** Used by node when doing `console.log(rs)`. */
|
|
171
238
|
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
172
|
-
return `RowState
|
|
239
|
+
return `RowState@${this.row.id}`;
|
|
173
240
|
}
|
|
174
241
|
}
|
|
175
242
|
exports.RowState = RowState;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { GridDataRow } from "../components/Row";
|
|
2
2
|
import { RowState } from "./RowState";
|
|
3
3
|
import { RowStorage } from "./RowStorage";
|
|
4
|
+
import { TableState } from "./TableState";
|
|
4
5
|
/**
|
|
5
6
|
* Manages our tree of observable RowStates that manage each GridDataRow's behavior.
|
|
6
7
|
*/
|
|
7
8
|
export declare class RowStates {
|
|
8
9
|
private map;
|
|
9
|
-
|
|
10
|
+
readonly table: TableState;
|
|
11
|
+
readonly storage: RowStorage;
|
|
12
|
+
private readonly header;
|
|
10
13
|
private keptGroupRow;
|
|
11
|
-
private header;
|
|
12
14
|
/** The first level of rows, i.e. not the header (or kept group), but the totals + top-level children. */
|
|
13
15
|
private topRows;
|
|
14
|
-
constructor();
|
|
16
|
+
constructor(table: TableState);
|
|
15
17
|
/** Returns a flat list of all of our RowStates. */
|
|
16
18
|
get allStates(): RowState[];
|
|
17
19
|
/** Returns the `RowState` for the given `id`. We should probably require `kind`. */
|
|
@@ -26,10 +28,11 @@ export declare class RowStates {
|
|
|
26
28
|
delete(ids: string[]): void;
|
|
27
29
|
/** Implements special collapse behavior, which is just the header's collapse/uncollapse. */
|
|
28
30
|
toggleCollapsed(id: string): void;
|
|
29
|
-
|
|
31
|
+
get visibleRows(): RowState[];
|
|
30
32
|
/** Returns kept rows, i.e. those that were user-selected but then client-side or server-side filtered. */
|
|
31
33
|
get keptRows(): RowState[];
|
|
32
34
|
get collapsedRows(): RowState[];
|
|
35
|
+
private createHeaderRow;
|
|
33
36
|
/** Create our synthetic "group row" for kept rows, that users never pass in, but we self-inject as needed. */
|
|
34
37
|
private createKeptGroupRow;
|
|
35
38
|
}
|
|
@@ -9,15 +9,19 @@ const utils_1 = require("./utils");
|
|
|
9
9
|
* Manages our tree of observable RowStates that manage each GridDataRow's behavior.
|
|
10
10
|
*/
|
|
11
11
|
class RowStates {
|
|
12
|
-
constructor() {
|
|
12
|
+
constructor(table) {
|
|
13
13
|
// A flat map of all row id -> RowState
|
|
14
14
|
this.map = new mobx_1.ObservableMap();
|
|
15
15
|
this.storage = new RowStorage_1.RowStorage(this);
|
|
16
|
+
// Pre-create the header to drive select-all/etc. behavior, even if the user
|
|
17
|
+
// doesn't pass an explicit `header` GridDataRow in `rows.props`
|
|
18
|
+
this.header = this.createHeaderRow();
|
|
16
19
|
// Pre-create our keptGroupRow for if/when we need it.
|
|
17
|
-
this.keptGroupRow = this.createKeptGroupRow();
|
|
18
|
-
this.header = undefined;
|
|
20
|
+
this.keptGroupRow = this.createKeptGroupRow(this.header);
|
|
19
21
|
/** The first level of rows, i.e. not the header (or kept group), but the totals + top-level children. */
|
|
20
22
|
this.topRows = [];
|
|
23
|
+
this.table = table;
|
|
24
|
+
this.map.set(this.header.row.id, this.header);
|
|
21
25
|
this.map.set(this.keptGroupRow.row.id, this.keptGroupRow);
|
|
22
26
|
}
|
|
23
27
|
/** Returns a flat list of all of our RowStates. */
|
|
@@ -37,43 +41,50 @@ class RowStates {
|
|
|
37
41
|
* Any missing rows are marked as `wasRemoved` so we can consider them "kept" if they're also selected.
|
|
38
42
|
*/
|
|
39
43
|
setRows(rows) {
|
|
40
|
-
const existing = new Set(this.map.values());
|
|
41
44
|
const states = this;
|
|
42
45
|
const map = this.map;
|
|
43
|
-
|
|
46
|
+
// Keep track of ids as we add them, to detect duplicates
|
|
47
|
+
const seenIds = new Set();
|
|
48
|
+
// Keep track of existing rows, so we can mark any that are missing as removed
|
|
49
|
+
const maybeKept = new Set(this.map.values());
|
|
50
|
+
function addRowAndChildren(parent, row) {
|
|
44
51
|
var _a;
|
|
45
|
-
// This should really be kind+id, but
|
|
52
|
+
// This should really be kind+id, but nearly all of our existing API uses just ids,
|
|
53
|
+
// b/c we assume our ids are tagged/unique across parent/child kinds anyway. So go
|
|
54
|
+
// ahead and enforce "row.id must be unique across kinds" b/c pragmatically that is
|
|
55
|
+
// baked into the current API signatures.
|
|
46
56
|
const key = row.id;
|
|
57
|
+
if (seenIds.has(key)) {
|
|
58
|
+
throw new Error(`Duplicate row id ${key}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
seenIds.add(key);
|
|
62
|
+
}
|
|
47
63
|
let state = map.get(key);
|
|
48
64
|
if (!state) {
|
|
49
|
-
state = new RowState_1.RowState(states, row);
|
|
65
|
+
state = new RowState_1.RowState(states, parent, row);
|
|
50
66
|
map.set(key, state);
|
|
51
67
|
}
|
|
52
68
|
else {
|
|
69
|
+
state.parent = parent;
|
|
53
70
|
state.row = row;
|
|
54
71
|
state.removed = false;
|
|
55
|
-
|
|
72
|
+
maybeKept.delete(state);
|
|
56
73
|
}
|
|
57
|
-
state.children = (_a = row.children) === null || _a === void 0 ? void 0 : _a.map((child) => addRowAndChildren(child));
|
|
74
|
+
state.children = (_a = row.children) === null || _a === void 0 ? void 0 : _a.map((child) => addRowAndChildren(state, child));
|
|
58
75
|
return state;
|
|
59
76
|
}
|
|
60
77
|
// Probe for the header row, so we can create it as a root RowState, even
|
|
61
78
|
// though we don't require the user to model their GridDataRows that way.
|
|
62
79
|
const headerRow = rows.find((r) => r.kind === utils_1.HEADER);
|
|
63
|
-
this.header = headerRow
|
|
80
|
+
this.header.row = headerRow || missingHeader;
|
|
64
81
|
// Add the top-level rows
|
|
65
|
-
this.topRows = rows.filter((row) => row !== headerRow).map((row) => addRowAndChildren(row));
|
|
66
|
-
//
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
// Always add the keptGroupRow, and we'll use keptGroupRow.isMatched=true/false to keep it
|
|
70
|
-
// from messing up "header is all selected" if its hidden/when there are no kept rows.
|
|
71
|
-
this.keptGroupRow,
|
|
72
|
-
...this.topRows.filter((rs) => !utils_1.reservedRowKinds.includes(rs.row.kind)),
|
|
73
|
-
];
|
|
74
|
-
}
|
|
82
|
+
this.topRows = rows.filter((row) => row !== headerRow).map((row) => addRowAndChildren(this.header, row));
|
|
83
|
+
// Always add the keptGroupRow, and we'll use keptGroupRow.isMatched=true/false to keep it
|
|
84
|
+
// from messing up "header is all selected" if its hidden/when there are no kept rows.
|
|
85
|
+
this.header.children = [this.keptGroupRow, ...this.topRows];
|
|
75
86
|
// Then mark any remaining as removed
|
|
76
|
-
for (const state of
|
|
87
|
+
for (const state of maybeKept)
|
|
77
88
|
state.markRemoved();
|
|
78
89
|
// After the first load of real data, we detach collapse state, to respect
|
|
79
90
|
// any incoming initCollapsed.
|
|
@@ -110,29 +121,24 @@ class RowStates {
|
|
|
110
121
|
}
|
|
111
122
|
else {
|
|
112
123
|
rs.toggleCollapsed();
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
// Ignore the kept group
|
|
125
|
+
const topGroupRows = this.topRows.filter((rs) => !rs.isReservedKind && rs.children);
|
|
126
|
+
if (topGroupRows.length > 0) {
|
|
127
|
+
// The header might still be collapsed, even though the user has opened all the top-level rows
|
|
128
|
+
if (topGroupRows.every((rs) => !rs.collapsed))
|
|
129
|
+
this.header.collapsed = false;
|
|
130
|
+
// Alternatively, if the user has collapsed all top-level rows, then collapse the header as well.
|
|
131
|
+
if (topGroupRows.every((rs) => rs.collapsed))
|
|
132
|
+
this.header.collapsed = true;
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!utils_1.reservedRowKinds.includes(rs.row.kind)) {
|
|
128
|
-
rs.isMatched = ids.includes(rs.row.id);
|
|
129
|
-
}
|
|
136
|
+
get visibleRows() {
|
|
137
|
+
const rows = this.header.selfAndMaybeChildren;
|
|
138
|
+
if (this.header.row === missingHeader) {
|
|
139
|
+
rows.splice(0, 1);
|
|
130
140
|
}
|
|
131
|
-
|
|
132
|
-
// literally matching the filter, or its children matching the filter (which is typically
|
|
133
|
-
// how the filter logic works), but if it just has any child rows at all (which actually means
|
|
134
|
-
// its children did _not_ match the filter, but are kept).
|
|
135
|
-
this.keptGroupRow.isMatched = this.keptRows.length > 0;
|
|
141
|
+
return rows;
|
|
136
142
|
}
|
|
137
143
|
/** Returns kept rows, i.e. those that were user-selected but then client-side or server-side filtered. */
|
|
138
144
|
get keptRows() {
|
|
@@ -141,8 +147,12 @@ class RowStates {
|
|
|
141
147
|
get collapsedRows() {
|
|
142
148
|
return this.allStates.filter((rs) => rs.collapsed);
|
|
143
149
|
}
|
|
150
|
+
createHeaderRow() {
|
|
151
|
+
// We'll switch the rs.row from the `missingHeader` to the real header from the props.rows later
|
|
152
|
+
return new RowState_1.RowState(this, undefined, missingHeader);
|
|
153
|
+
}
|
|
144
154
|
/** Create our synthetic "group row" for kept rows, that users never pass in, but we self-inject as needed. */
|
|
145
|
-
createKeptGroupRow() {
|
|
155
|
+
createKeptGroupRow(header) {
|
|
146
156
|
// The "group row" for selected rows that are hidden by filters and add the children
|
|
147
157
|
const keptGroupRow = {
|
|
148
158
|
id: utils_1.KEPT_GROUP,
|
|
@@ -151,11 +161,13 @@ class RowStates {
|
|
|
151
161
|
selectable: false,
|
|
152
162
|
data: undefined,
|
|
153
163
|
children: [],
|
|
164
|
+
pin: { at: "first", filter: true },
|
|
154
165
|
};
|
|
155
|
-
const rs = new RowState_1.RowState(this, keptGroupRow);
|
|
166
|
+
const rs = new RowState_1.RowState(this, header, keptGroupRow);
|
|
156
167
|
// Make the RowState behave like a parent, even though we calc its visibleChildren.
|
|
157
168
|
rs.children = [];
|
|
158
169
|
return rs;
|
|
159
170
|
}
|
|
160
171
|
}
|
|
161
172
|
exports.RowStates = RowStates;
|
|
173
|
+
const missingHeader = { kind: "header", id: "header", data: undefined };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RowStorage = void 0;
|
|
4
4
|
const mobx_1 = require("mobx");
|
|
5
|
-
const
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
6
|
/**
|
|
7
7
|
* Manages loading/saving our currently-collapsed rows to session storage.
|
|
8
8
|
*
|
|
@@ -20,7 +20,7 @@ class RowStorage {
|
|
|
20
20
|
}
|
|
21
21
|
load(persistCollapse) {
|
|
22
22
|
// Load what our previously collapsed rows were
|
|
23
|
-
this.historicalIds = (0,
|
|
23
|
+
this.historicalIds = (0, utils_1.loadArrayOrUndefined)(persistCollapse);
|
|
24
24
|
// And store new collapsed rows going forward
|
|
25
25
|
(0, mobx_1.reaction)(() => this.states.collapsedRows.map((rs) => rs.row.id), (rowIds) => sessionStorage.setItem(persistCollapse, JSON.stringify(rowIds)));
|
|
26
26
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { GridDataRow } from "../components/Row";
|
|
3
3
|
import { GridSortConfig } from "../GridTable";
|
|
4
|
+
import { GridTableApi } from "../GridTableApi";
|
|
4
5
|
import { Direction, GridColumnWithId } from "../types";
|
|
6
|
+
import { RowState } from "./RowState";
|
|
5
7
|
export type SelectedState = "checked" | "unchecked" | "partial";
|
|
6
8
|
/**
|
|
7
9
|
* Stores the collapsed & selected state of rows.
|
|
@@ -22,10 +24,13 @@ export declare class TableState {
|
|
|
22
24
|
private persistCollapse;
|
|
23
25
|
private rows;
|
|
24
26
|
columns: GridColumnWithId<any>[];
|
|
27
|
+
readonly api: GridTableApi<any>;
|
|
25
28
|
private readonly rowStates;
|
|
26
29
|
private readonly columnStates;
|
|
27
30
|
activeRowId: string | undefined;
|
|
28
31
|
activeCellId: string | undefined;
|
|
32
|
+
/** Stores the current client-side type-ahead search/filter. */
|
|
33
|
+
search: string[];
|
|
29
34
|
sortConfig: GridSortConfig | undefined;
|
|
30
35
|
sort: SortState;
|
|
31
36
|
private initialSortState;
|
|
@@ -33,13 +38,17 @@ export declare class TableState {
|
|
|
33
38
|
/**
|
|
34
39
|
* Creates the `RowState` for a given `GridTable`.
|
|
35
40
|
*/
|
|
36
|
-
constructor();
|
|
41
|
+
constructor(api: GridTableApi<any>);
|
|
37
42
|
loadCollapse(persistCollapse: string): void;
|
|
38
43
|
initSortState(sortConfig: GridSortConfig | undefined, columns: GridColumnWithId<any>[]): void;
|
|
39
44
|
setSortKey(clickedColumnId: string): void;
|
|
40
45
|
get sortState(): SortState | undefined;
|
|
46
|
+
/** Returns a client-side sort function, if applicable. */
|
|
47
|
+
get sortFn(): ((a: RowState, b: RowState) => number) | undefined;
|
|
41
48
|
setRows(rows: GridDataRow<any>[]): void;
|
|
42
49
|
setColumns(columns: GridColumnWithId<any>[], visibleColumnsStorageKey: string | undefined): void;
|
|
50
|
+
setSearch(search: string | undefined): void;
|
|
51
|
+
get visibleRows(): RowState[];
|
|
43
52
|
/** Returns visible columns, i.e. those that are visible + any expanded children. */
|
|
44
53
|
get visibleColumns(): GridColumnWithId<any>[];
|
|
45
54
|
/** Implements GridTableApi.visibleColumnIds. */
|
|
@@ -50,8 +59,6 @@ export declare class TableState {
|
|
|
50
59
|
toggleExpandedColumn(columnId: string): void;
|
|
51
60
|
numberOfExpandedChildren(columnId: string): number;
|
|
52
61
|
loadExpandedColumns(columnId: string): Promise<void>;
|
|
53
|
-
/** Called when GridTable has re-calced the rows that pass the client-side filter, or all rows. */
|
|
54
|
-
setMatchedRows(rowIds: string[]): void;
|
|
55
62
|
/** Returns selected data rows (non-header, non-totals, etc.), ignoring rows that have `row.selectable !== false`. */
|
|
56
63
|
get selectedRows(): GridDataRow<any>[];
|
|
57
64
|
/** Returns kept group row, with the latest kept children, if any. */
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.deriveSortState = exports.TableStateContext = exports.TableState = void 0;
|
|
7
7
|
const mobx_1 = require("mobx");
|
|
8
8
|
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const components_1 = require("../..");
|
|
9
10
|
const ColumnStates_1 = require("./ColumnStates");
|
|
10
11
|
const RowStates_1 = require("./RowStates");
|
|
11
12
|
const utils_1 = require("./utils");
|
|
@@ -28,26 +29,30 @@ class TableState {
|
|
|
28
29
|
/**
|
|
29
30
|
* Creates the `RowState` for a given `GridTable`.
|
|
30
31
|
*/
|
|
31
|
-
constructor() {
|
|
32
|
+
constructor(api) {
|
|
32
33
|
// The current list of rows, basically a useRef.current. Only shallow reactive.
|
|
33
34
|
this.rows = [];
|
|
34
35
|
// The current list of columns, basically a useRef.current. Only ref reactive.
|
|
35
36
|
this.columns = [];
|
|
36
|
-
this.rowStates = new RowStates_1.RowStates();
|
|
37
|
+
this.rowStates = new RowStates_1.RowStates(this);
|
|
37
38
|
this.columnStates = new ColumnStates_1.ColumnStates();
|
|
38
39
|
// Keeps track of the 'active' row, formatted `${row.kind}_${row.id}`
|
|
39
40
|
this.activeRowId = undefined;
|
|
40
41
|
// Keeps track of the 'active' cell, formatted `${row.kind}_${row.id}_${column.name}`
|
|
41
42
|
this.activeCellId = undefined;
|
|
43
|
+
/** Stores the current client-side type-ahead search/filter. */
|
|
44
|
+
this.search = [];
|
|
42
45
|
// Tracks the active sort column(s), so GridTable or SortHeaders can reactively
|
|
43
46
|
// re-render (for GridTable, only if client-side sorting)
|
|
44
47
|
this.sort = {};
|
|
48
|
+
this.api = api;
|
|
45
49
|
// Make ourselves an observable so that mobx will do caching of .collapseIds so
|
|
46
50
|
// that it'll be a stable identity for GridTable to useMemo against.
|
|
47
51
|
(0, mobx_1.makeAutoObservable)(this, {
|
|
48
52
|
// We use `ref`s so that observables can watch the immutable data change w/o deeply proxy-ifying Apollo fragments
|
|
49
53
|
rows: mobx_1.observable.ref,
|
|
50
54
|
columns: mobx_1.observable.ref,
|
|
55
|
+
search: mobx_1.observable.ref,
|
|
51
56
|
});
|
|
52
57
|
// If the kept rows went from empty to not empty, then introduce the SELECTED_GROUP row as collapsed
|
|
53
58
|
(0, mobx_1.reaction)(() => [...this.keptRows.values()], (curr, prev) => {
|
|
@@ -113,6 +118,15 @@ class TableState {
|
|
|
113
118
|
get sortState() {
|
|
114
119
|
return this.sort.current ? this.sort : undefined;
|
|
115
120
|
}
|
|
121
|
+
/** Returns a client-side sort function, if applicable. */
|
|
122
|
+
get sortFn() {
|
|
123
|
+
const { sortState, sortConfig, visibleColumns } = this;
|
|
124
|
+
if (!sortState || (sortConfig === null || sortConfig === void 0 ? void 0 : sortConfig.on) !== "client")
|
|
125
|
+
return undefined;
|
|
126
|
+
// sortRows.ts wants to sort based on the GridDataRow, so make a small `rowStateFn` adapter
|
|
127
|
+
const dataRowFn = (0, components_1.sortFn)(visibleColumns, sortState, !!sortConfig.caseSensitive);
|
|
128
|
+
return (a, b) => dataRowFn(a.row, b.row);
|
|
129
|
+
}
|
|
116
130
|
// Updates the list of rows and regenerates the collapsedRows property if needed.
|
|
117
131
|
setRows(rows) {
|
|
118
132
|
if (rows !== this.rows) {
|
|
@@ -126,6 +140,13 @@ class TableState {
|
|
|
126
140
|
this.columns = columns;
|
|
127
141
|
}
|
|
128
142
|
}
|
|
143
|
+
setSearch(search) {
|
|
144
|
+
// Break up "foo bar" into `[foo, bar]` and a row must match both `foo` and `bar`
|
|
145
|
+
this.search = (search && search.split(/ +/)) || [];
|
|
146
|
+
}
|
|
147
|
+
get visibleRows() {
|
|
148
|
+
return this.rowStates.visibleRows;
|
|
149
|
+
}
|
|
129
150
|
/** Returns visible columns, i.e. those that are visible + any expanded children. */
|
|
130
151
|
get visibleColumns() {
|
|
131
152
|
return this.columnStates.allVisibleColumns.map((cs) => cs.column);
|
|
@@ -154,10 +175,6 @@ class TableState {
|
|
|
154
175
|
loadExpandedColumns(columnId) {
|
|
155
176
|
return this.columnStates.get(columnId).doExpand();
|
|
156
177
|
}
|
|
157
|
-
/** Called when GridTable has re-calced the rows that pass the client-side filter, or all rows. */
|
|
158
|
-
setMatchedRows(rowIds) {
|
|
159
|
-
this.rowStates.setMatchedRows(rowIds);
|
|
160
|
-
}
|
|
161
178
|
/** Returns selected data rows (non-header, non-totals, etc.), ignoring rows that have `row.selectable !== false`. */
|
|
162
179
|
get selectedRows() {
|
|
163
180
|
return this.rowStates.allStates
|
|
@@ -4,4 +4,6 @@ import { GridDataRow } from "../components/Row";
|
|
|
4
4
|
import { GridColumnWithId, Kinded } from "../types";
|
|
5
5
|
import { SortOn, SortState } from "./TableState";
|
|
6
6
|
export declare function sortRows<R extends Kinded>(columns: GridColumnWithId<R>[], rows: GridDataRow<R>[], sortState: SortState, caseSensitive: boolean): GridDataRow<R>[];
|
|
7
|
+
/** Creates a comparator for two GridDataRows based on the current sortState. */
|
|
8
|
+
export declare function sortFn<R extends Kinded>(columns: GridColumnWithId<R>[], sortState: SortState, caseSensitive: boolean): (a: GridDataRow<R>, b: GridDataRow<R>) => number;
|
|
7
9
|
export declare function ensureClientSideSortValueIsSortable(sortOn: SortOn, isHeader: boolean, column: GridColumnWithId<any>, idx: number, maybeContent: ReactNode | GridCellContent): void;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureClientSideSortValueIsSortable = exports.sortRows = void 0;
|
|
3
|
+
exports.ensureClientSideSortValueIsSortable = exports.sortFn = exports.sortRows = void 0;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
5
|
// Returns a shallow copy of the `rows` parameter sorted based on `sortState`
|
|
6
|
+
// We really only use this for tests; in production the RowState.visibleSortedChildren uses the sortFn
|
|
6
7
|
function sortRows(columns, rows, sortState, caseSensitive) {
|
|
7
|
-
const
|
|
8
|
+
const fn = sortFn(columns, sortState, caseSensitive);
|
|
9
|
+
// Sort this level first
|
|
10
|
+
const sorted = [...rows].sort(fn);
|
|
8
11
|
// Recursively sort child rows
|
|
9
12
|
sorted.forEach((row, i) => {
|
|
10
13
|
if (row.children) {
|
|
@@ -14,7 +17,8 @@ function sortRows(columns, rows, sortState, caseSensitive) {
|
|
|
14
17
|
return sorted;
|
|
15
18
|
}
|
|
16
19
|
exports.sortRows = sortRows;
|
|
17
|
-
|
|
20
|
+
/** Creates a comparator for two GridDataRows based on the current sortState. */
|
|
21
|
+
function sortFn(columns, sortState, caseSensitive) {
|
|
18
22
|
// When client-side sort, the sort value is the column index
|
|
19
23
|
const { current, persistent } = sortState !== null && sortState !== void 0 ? sortState : {};
|
|
20
24
|
const { columnId, direction } = current !== null && current !== void 0 ? current : {};
|
|
@@ -23,8 +27,7 @@ function sortBatch(columns, batch, sortState, caseSensitive) {
|
|
|
23
27
|
const invert = direction === "DESC";
|
|
24
28
|
const primaryInvert = persistentSortDirection === "DESC";
|
|
25
29
|
const primaryColumn = persistentSortColumnId && columns.find((c) => c.id === persistentSortColumnId);
|
|
26
|
-
|
|
27
|
-
return [...batch].sort((a, b) => {
|
|
30
|
+
return (a, b) => {
|
|
28
31
|
if (a.pin || b.pin) {
|
|
29
32
|
// If both rows are pinned, we don't sort within them, because by pinning the page is taking
|
|
30
33
|
// explicit ownership over the order of the rows (and we also don't support "levels of pins",
|
|
@@ -39,13 +42,14 @@ function sortBatch(columns, batch, sortState, caseSensitive) {
|
|
|
39
42
|
// When primary key exist sort that priority first
|
|
40
43
|
const primaryCompare = compare(primaryColumn, a, b, primaryInvert, caseSensitive);
|
|
41
44
|
// if both rows are not primary sort equivalent
|
|
42
|
-
if (primaryCompare !== 0)
|
|
45
|
+
if (primaryCompare !== 0)
|
|
43
46
|
return primaryCompare;
|
|
44
|
-
|
|
47
|
+
// Fall through to the secondary sort
|
|
45
48
|
}
|
|
46
49
|
return column ? compare(column, a, b, invert, caseSensitive) : 0;
|
|
47
|
-
}
|
|
50
|
+
};
|
|
48
51
|
}
|
|
52
|
+
exports.sortFn = sortFn;
|
|
49
53
|
function getPin(pin) {
|
|
50
54
|
return typeof pin === "string" ? pin : pin === null || pin === void 0 ? void 0 : pin.at;
|
|
51
55
|
}
|