@homebound/beam 2.302.2 → 2.304.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/components/Table/GridTable.js +11 -6
- package/dist/components/Table/GridTableApi.d.ts +15 -13
- package/dist/components/Table/GridTableApi.js +3 -1
- package/dist/components/Table/components/EditColumnsButton.js +16 -15
- package/dist/components/Table/components/ExpandableHeader.js +1 -1
- package/dist/components/Table/components/Row.js +8 -8
- package/dist/components/Table/types.d.ts +7 -1
- package/dist/components/Table/utils/ColumnState.d.ts +24 -0
- package/dist/components/Table/utils/ColumnState.js +82 -0
- package/dist/components/Table/utils/ColumnStates.d.ts +29 -0
- package/dist/components/Table/utils/ColumnStates.js +79 -0
- package/dist/components/Table/utils/ColumnStorage.d.ts +13 -0
- package/dist/components/Table/utils/ColumnStorage.js +37 -0
- package/dist/components/Table/utils/RowState.d.ts +27 -6
- package/dist/components/Table/utils/RowState.js +41 -7
- package/dist/components/Table/utils/RowStates.d.ts +13 -2
- package/dist/components/Table/utils/RowStates.js +73 -29
- package/dist/components/Table/utils/RowStorage.d.ts +27 -0
- package/dist/components/Table/utils/RowStorage.js +42 -0
- package/dist/components/Table/utils/TableState.d.ts +10 -15
- package/dist/components/Table/utils/TableState.js +40 -245
- package/dist/components/Table/utils/columns.d.ts +1 -1
- package/dist/components/Table/utils/columns.js +9 -4
- package/dist/components/Table/utils/utils.d.ts +2 -0
- package/dist/components/Table/utils/utils.js +7 -1
- package/package.json +1 -1
|
@@ -99,17 +99,21 @@ function GridTable(props) {
|
|
|
99
99
|
api.init(persistCollapse, virtuosoRef, rows);
|
|
100
100
|
api.setActiveRowId(activeRowId);
|
|
101
101
|
api.setActiveCellId(activeCellId);
|
|
102
|
+
// Push the initial columns directly into tableState, b/c that is what
|
|
103
|
+
// makes the tests pass, but then further updates we'll do through useEffect
|
|
104
|
+
// to avoid "Cannot update component during render" errors.
|
|
105
|
+
api.tableState.setColumns(columnsWithIds, visibleColumnsStorageKey);
|
|
102
106
|
return api;
|
|
103
107
|
}, [props.api]);
|
|
104
108
|
const style = (0, TableStyles_1.resolveStyles)(maybeStyle);
|
|
105
109
|
const { tableState } = api;
|
|
106
110
|
tableState.setRows(rows);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
(0, react_1.useEffect)(() => {
|
|
112
|
+
tableState.setColumns(columnsWithIds, visibleColumnsStorageKey);
|
|
113
|
+
}, [tableState, columnsWithIds, visibleColumnsStorageKey]);
|
|
114
|
+
const columns = (0, hooks_1.useComputed)(() => {
|
|
115
|
+
return tableState.visibleColumns;
|
|
116
|
+
}, [tableState]);
|
|
113
117
|
// Initialize the sort state. This will only happen on the first render.
|
|
114
118
|
// Once the `TableState.sort` is defined, it will not re-initialize.
|
|
115
119
|
tableState.initSortState(props.sorting, columns);
|
|
@@ -128,6 +132,7 @@ function GridTable(props) {
|
|
|
128
132
|
// (or us) is resetting component state more than necessary, so we track render counts from
|
|
129
133
|
// here instead.
|
|
130
134
|
const { getCount } = (0, useRenderCount_1.useRenderCount)();
|
|
135
|
+
// Our column sizes use either `w` or `expandedWidth`, so see which columns are currently expanded
|
|
131
136
|
const expandedColumnIds = (0, hooks_1.useComputed)(() => tableState.expandedColumnIds, [tableState]);
|
|
132
137
|
const columnSizes = (0, useSetupColumnSizes_1.useSetupColumnSizes)(style, columns, resizeTarget !== null && resizeTarget !== void 0 ? resizeTarget : resizeRef, expandedColumnIds);
|
|
133
138
|
// Make a single copy of our current collapsed state, so we'll have a single observer.
|
|
@@ -22,28 +22,30 @@ export declare function useGridTableApi<R extends Kinded>(): GridTableApi<R>;
|
|
|
22
22
|
/** Provides an imperative API for an application page to interact with the table. */
|
|
23
23
|
export type GridTableApi<R extends Kinded> = {
|
|
24
24
|
/** Scrolls row `index` into view; only supported with `as=virtual` and after a `useEffect`. */
|
|
25
|
-
scrollToIndex
|
|
25
|
+
scrollToIndex(index: number): void;
|
|
26
26
|
/** Returns the ids of currently-selected rows. */
|
|
27
27
|
getSelectedRowIds(): string[];
|
|
28
28
|
getSelectedRowIds<K extends R["kind"]>(kind: K): string[];
|
|
29
29
|
/** Returns the currently-selected rows. */
|
|
30
30
|
getSelectedRows(): GridDataRow<R>[];
|
|
31
|
+
/** Returns the currently-selected rows of the given `kind`. */
|
|
31
32
|
getSelectedRows<K extends R["kind"]>(kind: K): GridDataRow<DiscriminateUnion<R, "kind", K>>[];
|
|
32
|
-
/**
|
|
33
|
+
/** Set selected state of a row by id. */
|
|
34
|
+
selectRow(id: string, selected?: boolean): void;
|
|
35
|
+
/** De-selects all selected rows. */
|
|
33
36
|
clearSelections(): void;
|
|
37
|
+
/** Whether a row is currently collapsed. */
|
|
38
|
+
isCollapsedRow(id: string): boolean;
|
|
39
|
+
/** Toggle collapse state of a row by id. */
|
|
40
|
+
toggleCollapsedRow(id: string): void;
|
|
34
41
|
/** Sets the internal state of 'activeRowId' */
|
|
35
|
-
setActiveRowId
|
|
42
|
+
setActiveRowId(id: string | undefined): void;
|
|
36
43
|
/** Sets the internal state of 'activeCellId' */
|
|
37
|
-
setActiveCellId
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/** Toggle collapse state of a row by id */
|
|
43
|
-
toggleCollapsedRow: (id: string) => void;
|
|
44
|
-
isCollapsedRow: (id: string) => boolean;
|
|
45
|
-
setVisibleColumns: (ids: string[]) => void;
|
|
46
|
-
getVisibleColumnIds: () => string[];
|
|
44
|
+
setActiveCellId(id: string | undefined): void;
|
|
45
|
+
/** Deletes a row from the table, i.e. so it's not detected as kept. */
|
|
46
|
+
deleteRows(ids: string[]): void;
|
|
47
|
+
getVisibleColumnIds(): string[];
|
|
48
|
+
setVisibleColumns(ids: string[]): void;
|
|
47
49
|
};
|
|
48
50
|
export declare class GridTableApiImpl<R extends Kinded> implements GridTableApi<R> {
|
|
49
51
|
readonly tableState: TableState;
|
|
@@ -31,7 +31,9 @@ class GridTableApiImpl {
|
|
|
31
31
|
}
|
|
32
32
|
/** Called once by the GridTable when it takes ownership of this api instance. */
|
|
33
33
|
init(persistCollapse, virtuosoRef, rows) {
|
|
34
|
-
this
|
|
34
|
+
// Technically this drives both row-collapse and column-expanded
|
|
35
|
+
if (persistCollapse)
|
|
36
|
+
this.tableState.loadCollapse(persistCollapse);
|
|
35
37
|
this.virtuosoRef = virtuosoRef;
|
|
36
38
|
}
|
|
37
39
|
scrollToIndex(index) {
|
|
@@ -24,22 +24,23 @@ function EditColumnsButton(props) {
|
|
|
24
24
|
: (0, OverlayTrigger_1.isIconButton)(trigger)
|
|
25
25
|
? trigger.icon
|
|
26
26
|
: trigger.name);
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}, [columns]);
|
|
27
|
+
const options = (0, react_1.useMemo)(() => columns
|
|
28
|
+
// Only include options that can be hidden
|
|
29
|
+
.filter((column) => column.canHide)
|
|
30
|
+
// And have the `name` property defined
|
|
31
|
+
.filter((column) => {
|
|
32
|
+
if (!column.name || column.name.length === 0 || !column.id || column.id.length === 0) {
|
|
33
|
+
console.warn("Column is missing 'name' and/or 'id' property required by the Edit Columns button", column);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
})
|
|
38
|
+
.map((column) => ({ label: column.name, value: column.id })), [columns]);
|
|
40
39
|
const selectedValues = (0, hooks_1.useComputed)(() => api.getVisibleColumnIds(), [api]);
|
|
41
|
-
const setSelectedValues = (0, react_1.useCallback)((
|
|
42
|
-
|
|
40
|
+
const setSelectedValues = (0, react_1.useCallback)((ids) => {
|
|
41
|
+
// Doesn't `options` already filter us to non-hidden/valid-id columns? I.e. could we just do:
|
|
42
|
+
// api.setVisibleColumns(ids);
|
|
43
|
+
api.setVisibleColumns(columns.filter((column) => (column.canHide ? ids.includes(column.id) : true)).map((c) => c.id));
|
|
43
44
|
}, [columns, api]);
|
|
44
45
|
return ((0, jsx_runtime_1.jsx)(OverlayTrigger_1.OverlayTrigger, { ...props, menuTriggerProps: menuTriggerProps, state: state, buttonRef: buttonRef, ...tid, children: (0, jsx_runtime_1.jsxs)("div", { css: {
|
|
45
46
|
...Css_1.Css.bgWhite.py5.px3.maxwPx(380).bshBasic.$,
|
|
@@ -22,7 +22,7 @@ function ExpandableHeader(props) {
|
|
|
22
22
|
return ((0, jsx_runtime_1.jsxs)("button", { ...hoverProps, css: Css_1.Css.df.xsMd.aic.jcsb.gap2.px1.hPx(32).mxPx(-8).w("calc(100% + 16px)").br4.lightBlue700.if(isHovered).bgGray100.$, onClick: async () => {
|
|
23
23
|
if ((0, utils_2.isFunction)(column.expandColumns)) {
|
|
24
24
|
setIsLoading(true);
|
|
25
|
-
await tableState.loadExpandedColumns(column);
|
|
25
|
+
await tableState.loadExpandedColumns(column.id);
|
|
26
26
|
setIsLoading(false);
|
|
27
27
|
}
|
|
28
28
|
// manually calling this as loadExpandedColumns does not toggle
|
|
@@ -78,11 +78,11 @@ function RowImpl(props) {
|
|
|
78
78
|
let minStickyLeftOffset = 0;
|
|
79
79
|
let expandColumnHidden = false;
|
|
80
80
|
return ((0, jsx_runtime_1.jsx)(RowTag, { css: rowCss, ...others, "data-gridrow": true, ...getCount(row.id), children: isKeptGroupRow ? ((0, jsx_runtime_1.jsx)(KeptGroupRow_1.KeptGroupRow, { as: as, style: style, columnSizes: columnSizes, row: row, colSpan: columns.length })) : (columns.map((column, columnIndex) => {
|
|
81
|
-
var _a, _b, _c, _d
|
|
81
|
+
var _a, _b, _c, _d;
|
|
82
82
|
// If the expandable column was hidden, then we need to look at the previous column to format the `expandHeader` and 'header' kinds correctly.
|
|
83
83
|
const maybeExpandedColumn = expandColumnHidden ? columns[columnIndex - 1] : column;
|
|
84
84
|
// Figure out if this column should be considered 'expanded' or not. If the column is hidden on expand, then we need to look at the previous column to see if it's expanded.
|
|
85
|
-
const isExpanded = tableState.
|
|
85
|
+
const isExpanded = tableState.isExpandedColumn(maybeExpandedColumn.id);
|
|
86
86
|
// If the column is hidden on expand, we don't want to render it. We'll flag that it was hidden, so on the next column we can render this column's "expandHeader" property.
|
|
87
87
|
if (column.hideOnExpand && isExpanded) {
|
|
88
88
|
expandColumnHidden = true;
|
|
@@ -90,9 +90,9 @@ function RowImpl(props) {
|
|
|
90
90
|
}
|
|
91
91
|
// Need to keep track of the expanded columns so we can add borders as expected for the header rows
|
|
92
92
|
const numExpandedColumns = isExpanded
|
|
93
|
-
?
|
|
93
|
+
? tableState.numberOfExpandedChildren(maybeExpandedColumn.id)
|
|
94
94
|
? // Subtract 1 if the column is hidden on expand, since we're not rendering it.
|
|
95
|
-
tableState.
|
|
95
|
+
tableState.numberOfExpandedChildren(maybeExpandedColumn.id) - (maybeExpandedColumn.hideOnExpand ? 1 : 0)
|
|
96
96
|
: 0
|
|
97
97
|
: 0;
|
|
98
98
|
// If we're rendering the Expandable Header row, then we might need to render the previous column's `expandHeader` property in the case where the column is hidden on expand.
|
|
@@ -145,7 +145,7 @@ function RowImpl(props) {
|
|
|
145
145
|
column.expandedWidth !== undefined;
|
|
146
146
|
const content = (0, utils_1.toContent)(maybeContent, isHeader, canSortColumn, sortOn === "client", style, as, alignment, column, isExpandableHeader, isExpandable, minStickyLeftOffset, isKeptSelectedRow);
|
|
147
147
|
(0, sortRows_1.ensureClientSideSortValueIsSortable)(sortOn, isHeader || isTotals || isExpandableHeader, column, columnIndex, maybeContent);
|
|
148
|
-
const maybeSticky = (
|
|
148
|
+
const maybeSticky = (_a = (((0, utils_1.isGridCellContent)(maybeContent) && maybeContent.sticky) || column.sticky)) !== null && _a !== void 0 ? _a : undefined;
|
|
149
149
|
const maybeStickyColumnStyles = maybeSticky && columnSizes
|
|
150
150
|
? {
|
|
151
151
|
...Css_1.Css.sticky.z(utils_1.zIndices.stickyColumns).bgWhite.$,
|
|
@@ -199,13 +199,13 @@ function RowImpl(props) {
|
|
|
199
199
|
currentExpandedColumnCount === 0 &&
|
|
200
200
|
Css_1.Css.boxShadow(`inset -1px -1px 0 ${Css_1.Palette.Gray200}`).$),
|
|
201
201
|
// Or level-specific styling
|
|
202
|
-
...(!isHeader && !isTotals && !isExpandableHeader && !!style.levels && ((
|
|
202
|
+
...(!isHeader && !isTotals && !isExpandableHeader && !!style.levels && ((_b = style.levels[level]) === null || _b === void 0 ? void 0 : _b.cellCss)),
|
|
203
203
|
// Level specific styling for the first content column
|
|
204
|
-
...(applyFirstContentColumnStyles && !!style.levels && ((
|
|
204
|
+
...(applyFirstContentColumnStyles && !!style.levels && ((_c = style.levels[level]) === null || _c === void 0 ? void 0 : _c.firstContentColumn)),
|
|
205
205
|
// The specific cell's css (if any from GridCellContent)
|
|
206
206
|
...rowStyleCellCss,
|
|
207
207
|
// Apply active row styling for non-nested card styles.
|
|
208
|
-
...(isActive ? Css_1.Css.bgColor((
|
|
208
|
+
...(isActive ? Css_1.Css.bgColor((_d = style.activeBgColor) !== null && _d !== void 0 ? _d : Css_1.Palette.LightBlue50).$ : {}),
|
|
209
209
|
// Add any cell specific style overrides
|
|
210
210
|
...((0, utils_1.isGridCellContent)(maybeContent) && maybeContent.typeScale ? Css_1.Css[maybeContent.typeScale].$ : {}),
|
|
211
211
|
// And any cell specific css
|
|
@@ -85,6 +85,12 @@ export type GridColumn<R extends Kinded> = {
|
|
|
85
85
|
/** Determines whether this column should be hidden when expanded (only the 'expandColumns' would show) */
|
|
86
86
|
hideOnExpand?: boolean;
|
|
87
87
|
};
|
|
88
|
+
/**
|
|
89
|
+
* Adds an `id` to `GridColumn`, for use in storage/APIs.
|
|
90
|
+
*
|
|
91
|
+
* Ideally we'd require this on `GridColumn` itself, but that would be
|
|
92
|
+
* a large breaking change for a lot of tables that don't need column ids.
|
|
93
|
+
*/
|
|
88
94
|
export type GridColumnWithId<R extends Kinded> = GridColumn<R> & {
|
|
89
95
|
id: string;
|
|
90
96
|
expandColumns?: GridColumnWithId<R>[] | (() => Promise<GridColumn<R>[]>);
|
|
@@ -102,6 +108,6 @@ export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
|
|
|
102
108
|
export type InfiniteScroll = {
|
|
103
109
|
/** will be called when the user scrolls to the end of the list with the last item index as an argument. */
|
|
104
110
|
onEndReached: (index: number) => void;
|
|
105
|
-
/** The number of pixels from the bottom of the list to eagerly trigger `onEndReached`. The default is
|
|
111
|
+
/** The number of pixels from the bottom of the list to eagerly trigger `onEndReached`. The default is 500px. */
|
|
106
112
|
endOffsetPx?: number;
|
|
107
113
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GridColumnWithId } from "../../..";
|
|
2
|
+
import { ColumnStates } from "./ColumnStates";
|
|
3
|
+
import { ColumnStorage } from "./ColumnStorage";
|
|
4
|
+
/**
|
|
5
|
+
* A reactive/observable wrapper around each GridColumn.
|
|
6
|
+
*
|
|
7
|
+
* This is primarily for tracking visible/expanded columns for tables
|
|
8
|
+
* that use the expandable columns feature.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ColumnState {
|
|
11
|
+
private states;
|
|
12
|
+
column: GridColumnWithId<any>;
|
|
13
|
+
children: ColumnState[] | undefined;
|
|
14
|
+
private visible;
|
|
15
|
+
private expanded;
|
|
16
|
+
constructor(states: ColumnStates, storage: ColumnStorage, column: GridColumnWithId<any>);
|
|
17
|
+
setVisible(visible: boolean): void;
|
|
18
|
+
get isExpanded(): boolean;
|
|
19
|
+
toggleExpanded(): void;
|
|
20
|
+
/** Calls the `column.expandColumns` function, if set, and adds the resulting columns. */
|
|
21
|
+
doExpand(force?: boolean): Promise<void>;
|
|
22
|
+
/** Returns this column, if visible, and its children, if expanded. */
|
|
23
|
+
get maybeSelfAndChildren(): ColumnState[];
|
|
24
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ColumnState = void 0;
|
|
4
|
+
const mobx_1 = require("mobx");
|
|
5
|
+
const src_1 = require("../../..");
|
|
6
|
+
const index_1 = require("../../../utils/index");
|
|
7
|
+
/**
|
|
8
|
+
* A reactive/observable wrapper around each GridColumn.
|
|
9
|
+
*
|
|
10
|
+
* This is primarily for tracking visible/expanded columns for tables
|
|
11
|
+
* that use the expandable columns feature.
|
|
12
|
+
*/
|
|
13
|
+
class ColumnState {
|
|
14
|
+
constructor(states, storage, column) {
|
|
15
|
+
var _a, _b, _c;
|
|
16
|
+
this.states = states;
|
|
17
|
+
this.children = undefined;
|
|
18
|
+
this.visible = true;
|
|
19
|
+
this.expanded = false;
|
|
20
|
+
this.column = column;
|
|
21
|
+
// If the user sets `canHide: true`, we default to hidden unless they set `initVisible: true`
|
|
22
|
+
this.visible = (_a = storage.wasVisible(column.id)) !== null && _a !== void 0 ? _a : (column.canHide ? (_b = column.initVisible) !== null && _b !== void 0 ? _b : false : true);
|
|
23
|
+
if (this.visible && ((_c = storage.wasExpanded(column.id)) !== null && _c !== void 0 ? _c : column.initExpanded)) {
|
|
24
|
+
this.expanded = true;
|
|
25
|
+
this.doExpand();
|
|
26
|
+
}
|
|
27
|
+
(0, mobx_1.makeAutoObservable)(this, { column: mobx_1.observable.ref });
|
|
28
|
+
}
|
|
29
|
+
setVisible(visible) {
|
|
30
|
+
const wasVisible = this.visible;
|
|
31
|
+
this.visible = visible;
|
|
32
|
+
// If an expandable header is becoming visible for the 1st time, expand it
|
|
33
|
+
if (!wasVisible && visible && this.column.initExpanded && this.children === undefined) {
|
|
34
|
+
this.expanded = true;
|
|
35
|
+
this.doExpand();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
get isExpanded() {
|
|
39
|
+
return this.expanded;
|
|
40
|
+
}
|
|
41
|
+
toggleExpanded() {
|
|
42
|
+
const wasExpanded = this.expanded;
|
|
43
|
+
this.expanded = !this.expanded;
|
|
44
|
+
// The first time we expand, fetch our children. Note that ExpandableHeader
|
|
45
|
+
// technically pre-loads our children, so it can show a spinner while loading,
|
|
46
|
+
// and only after loading is complete, tell our column to expand.
|
|
47
|
+
if (!wasExpanded)
|
|
48
|
+
this.doExpand();
|
|
49
|
+
}
|
|
50
|
+
/** Calls the `column.expandColumns` function, if set, and adds the resulting columns. */
|
|
51
|
+
async doExpand(force = false) {
|
|
52
|
+
const { expandColumns } = this.column;
|
|
53
|
+
// If we've already got the children, don't re-expand unless forced (i.e. props.columns changed)
|
|
54
|
+
if (this.children !== undefined && !force)
|
|
55
|
+
return;
|
|
56
|
+
if ((0, index_1.isFunction)(expandColumns)) {
|
|
57
|
+
const ecs = await expandColumns();
|
|
58
|
+
this.children = (0, src_1.assignDefaultColumnIds)(ecs).map((ec) => this.states.addColumn(ec));
|
|
59
|
+
}
|
|
60
|
+
else if (expandColumns) {
|
|
61
|
+
this.children = expandColumns.map((ec) => this.states.addColumn(ec));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Returns this column, if visible, and its children, if expanded. */
|
|
65
|
+
get maybeSelfAndChildren() {
|
|
66
|
+
if (!this.visible) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
else if (this.expanded && this.children) {
|
|
70
|
+
// Maybe do the `hideOnExpand` thing here? Seems cute, but the Row rendering still
|
|
71
|
+
// needs to do the "look back to the prior column for the expandableHeader cell" logic.
|
|
72
|
+
// if (this.column.hideOnExpand) {
|
|
73
|
+
// return this.children.flatMap((c) => c.selfAndMaybeChildren);
|
|
74
|
+
// }
|
|
75
|
+
return [this, ...this.children.flatMap((c) => c.maybeSelfAndChildren)];
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return [this];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.ColumnState = ColumnState;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GridColumnWithId } from "../../..";
|
|
2
|
+
import { ColumnState } from "./ColumnState";
|
|
3
|
+
/** A reactive/observable wrapper around our columns. */
|
|
4
|
+
export declare class ColumnStates {
|
|
5
|
+
private columns;
|
|
6
|
+
private map;
|
|
7
|
+
private storage;
|
|
8
|
+
constructor();
|
|
9
|
+
/**
|
|
10
|
+
* Updates our internal columns states when `props.columns` changes.
|
|
11
|
+
*
|
|
12
|
+
* We handle sessionStorage here b/c we allow the user to either provide their own
|
|
13
|
+
* storage key, or calc the storage key based on the currently-visible columns.
|
|
14
|
+
* So like you expand a column, and new columns show up, but we'll remember they
|
|
15
|
+
* were hidden last time you looked at this specific expansion of columns.
|
|
16
|
+
*/
|
|
17
|
+
setColumns(columns: GridColumnWithId<any>[], visibleColumnsStorageKey: string | undefined): void;
|
|
18
|
+
/** Adds a column to our state, i.e. maybe a dynamically loaded column. */
|
|
19
|
+
addColumn(column: GridColumnWithId<any>): ColumnState;
|
|
20
|
+
/** Returns the `ColumnState` for the given `id`. */
|
|
21
|
+
get(id: string): ColumnState;
|
|
22
|
+
/** Returns all currently-expanded columns. */
|
|
23
|
+
get expandedColumns(): ColumnState[];
|
|
24
|
+
/** Returns a flat list of all visible columns. */
|
|
25
|
+
get allVisibleColumns(): ColumnState[];
|
|
26
|
+
setVisibleColumns(ids: string[]): void;
|
|
27
|
+
loadExpanded(storageKey: string): void;
|
|
28
|
+
loadVisible(storageKey: string): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ColumnStates = void 0;
|
|
4
|
+
const change_case_1 = require("change-case");
|
|
5
|
+
const mobx_1 = require("mobx");
|
|
6
|
+
const ColumnState_1 = require("./ColumnState");
|
|
7
|
+
const ColumnStorage_1 = require("./ColumnStorage");
|
|
8
|
+
/** A reactive/observable wrapper around our columns. */
|
|
9
|
+
class ColumnStates {
|
|
10
|
+
constructor() {
|
|
11
|
+
// The top-level list of columns
|
|
12
|
+
this.columns = [];
|
|
13
|
+
this.map = new Map();
|
|
14
|
+
this.storage = new ColumnStorage_1.ColumnStorage(this);
|
|
15
|
+
(0, mobx_1.makeAutoObservable)(this);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Updates our internal columns states when `props.columns` changes.
|
|
19
|
+
*
|
|
20
|
+
* We handle sessionStorage here b/c we allow the user to either provide their own
|
|
21
|
+
* storage key, or calc the storage key based on the currently-visible columns.
|
|
22
|
+
* So like you expand a column, and new columns show up, but we'll remember they
|
|
23
|
+
* were hidden last time you looked at this specific expansion of columns.
|
|
24
|
+
*/
|
|
25
|
+
setColumns(columns, visibleColumnsStorageKey) {
|
|
26
|
+
if (columns.some((c) => c.canHide)) {
|
|
27
|
+
// We optionally auto-calc visible columns based on the currently-_potentially_-visible columns
|
|
28
|
+
visibleColumnsStorageKey !== null && visibleColumnsStorageKey !== void 0 ? visibleColumnsStorageKey : (visibleColumnsStorageKey = (0, change_case_1.camelCase)(columns.map((c) => c.id).join()));
|
|
29
|
+
this.loadVisible(visibleColumnsStorageKey);
|
|
30
|
+
}
|
|
31
|
+
this.columns = columns.map((c) => this.addColumn(c));
|
|
32
|
+
// After the very first non-zero `setColumns`, we disconnect from sessionStorage
|
|
33
|
+
if (columns.length > 0)
|
|
34
|
+
this.storage.done();
|
|
35
|
+
}
|
|
36
|
+
/** Adds a column to our state, i.e. maybe a dynamically loaded column. */
|
|
37
|
+
addColumn(column) {
|
|
38
|
+
const existing = this.map.get(column.id);
|
|
39
|
+
if (!existing) {
|
|
40
|
+
const cs = new ColumnState_1.ColumnState(this, this.storage, column);
|
|
41
|
+
this.map.set(column.id, cs);
|
|
42
|
+
return cs;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
existing.column = column;
|
|
46
|
+
// Any time a column is re-added (i.e. props.columns changed), re-expand it
|
|
47
|
+
if (existing.isExpanded)
|
|
48
|
+
existing.doExpand(true);
|
|
49
|
+
return existing;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Returns the `ColumnState` for the given `id`. */
|
|
53
|
+
get(id) {
|
|
54
|
+
const cs = this.map.get(id);
|
|
55
|
+
if (!cs)
|
|
56
|
+
throw new Error(`No ColumnState for ${id}`);
|
|
57
|
+
return cs;
|
|
58
|
+
}
|
|
59
|
+
/** Returns all currently-expanded columns. */
|
|
60
|
+
get expandedColumns() {
|
|
61
|
+
return this.columns.filter((cs) => cs.isExpanded);
|
|
62
|
+
}
|
|
63
|
+
/** Returns a flat list of all visible columns. */
|
|
64
|
+
get allVisibleColumns() {
|
|
65
|
+
return this.columns.flatMap((cs) => cs.maybeSelfAndChildren);
|
|
66
|
+
}
|
|
67
|
+
setVisibleColumns(ids) {
|
|
68
|
+
for (const cs of this.map.values()) {
|
|
69
|
+
cs.setVisible(ids.includes(cs.column.id));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
loadExpanded(storageKey) {
|
|
73
|
+
this.storage.loadExpanded(storageKey);
|
|
74
|
+
}
|
|
75
|
+
loadVisible(storageKey) {
|
|
76
|
+
this.storage.loadVisible(storageKey);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.ColumnStates = ColumnStates;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ColumnStates } from "./ColumnStates";
|
|
2
|
+
/** Loads/saves the column state from sessionStorage. */
|
|
3
|
+
export declare class ColumnStorage {
|
|
4
|
+
private states;
|
|
5
|
+
private expandedIds;
|
|
6
|
+
private visibleIds;
|
|
7
|
+
constructor(states: ColumnStates);
|
|
8
|
+
loadExpanded(persistCollapse: string): void;
|
|
9
|
+
loadVisible(storageKey: string): void;
|
|
10
|
+
wasExpanded(id: string): boolean | undefined;
|
|
11
|
+
wasVisible(id: string): boolean | undefined;
|
|
12
|
+
done(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ColumnStorage = void 0;
|
|
4
|
+
const mobx_1 = require("mobx");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
/** Loads/saves the column state from sessionStorage. */
|
|
7
|
+
class ColumnStorage {
|
|
8
|
+
constructor(states) {
|
|
9
|
+
this.states = states;
|
|
10
|
+
}
|
|
11
|
+
loadExpanded(persistCollapse) {
|
|
12
|
+
const key = `expandedColumn_${persistCollapse}`;
|
|
13
|
+
this.expandedIds = (0, utils_1.loadArrayOrUndefined)(key);
|
|
14
|
+
(0, mobx_1.reaction)(() => this.states.expandedColumns.map((cs) => cs.column.id), (columnIds) => sessionStorage.setItem(key, JSON.stringify(columnIds)));
|
|
15
|
+
}
|
|
16
|
+
loadVisible(storageKey) {
|
|
17
|
+
this.visibleIds = (0, utils_1.loadArrayOrUndefined)(storageKey);
|
|
18
|
+
// Unlike the others, where we only store the value on change, we immediately
|
|
19
|
+
// store this value (but I'm not sure why...), hence using `autorun`.
|
|
20
|
+
(0, mobx_1.autorun)(() => {
|
|
21
|
+
const columnIds = this.states.allVisibleColumns.map((cs) => cs.column.id);
|
|
22
|
+
sessionStorage.setItem(storageKey, JSON.stringify(columnIds));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
wasExpanded(id) {
|
|
26
|
+
var _a;
|
|
27
|
+
return (_a = this.expandedIds) === null || _a === void 0 ? void 0 : _a.includes(id);
|
|
28
|
+
}
|
|
29
|
+
wasVisible(id) {
|
|
30
|
+
var _a;
|
|
31
|
+
return (_a = this.visibleIds) === null || _a === void 0 ? void 0 : _a.includes(id);
|
|
32
|
+
}
|
|
33
|
+
done() {
|
|
34
|
+
this.expandedIds = undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.ColumnStorage = ColumnStorage;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GridDataRow, SelectedState } from "../../..";
|
|
2
|
+
import { RowStates } from "./RowStates";
|
|
2
3
|
/**
|
|
3
4
|
* A reactive/observable state of each GridDataRow's current behavior.
|
|
4
5
|
*
|
|
@@ -8,19 +9,36 @@ import { GridDataRow, SelectedState } from "../../..";
|
|
|
8
9
|
export declare class RowState {
|
|
9
10
|
/** Our row, only ref observed, so we don't crawl into GraphQL fragments. */
|
|
10
11
|
row: GridDataRow<any>;
|
|
11
|
-
/** Our parent RowState, or the `header` RowState if we're a top-level row. */
|
|
12
|
-
parent: RowState | undefined;
|
|
13
12
|
/** Our children row states, as of the latest `props.rows`, without any filtering applied. */
|
|
14
13
|
children: RowState[] | undefined;
|
|
15
14
|
/** Whether we match a client-side filter; true if no filter is in place. */
|
|
16
15
|
isMatched: boolean;
|
|
17
16
|
/** Whether we are *directly* selected. */
|
|
18
17
|
selected: boolean;
|
|
19
|
-
/** Whether
|
|
20
|
-
|
|
21
|
-
constructor(parent: RowState | undefined, row: GridDataRow<any>);
|
|
18
|
+
/** Whether we are collapsed. */
|
|
19
|
+
collapsed: boolean;
|
|
22
20
|
/**
|
|
23
|
-
* Whether
|
|
21
|
+
* Whether our `row` had been in `props.rows`, but then removed _while being
|
|
22
|
+
* selected_, i.e. potentially by server-side filters.
|
|
23
|
+
*
|
|
24
|
+
* We have had a large foot-gun where users "select a row", change the filters,
|
|
25
|
+
* the row disappears (filtered out), and the user clicks "Go!", but the table
|
|
26
|
+
* thinks their previously-selected row is gone (b/c it's not in view), and
|
|
27
|
+
* then the row is inappropriately deleted/unassociated/etc. (b/c in the user's
|
|
28
|
+
* head, it is "still selected").
|
|
29
|
+
*
|
|
30
|
+
* To avoid this, we by default keep selected rows, as "kept rows", to make
|
|
31
|
+
* extra sure the user wants them to go away.
|
|
32
|
+
*
|
|
33
|
+
* Soft-deleted rows are rows that were removed from `props.rows` (i.e. we
|
|
34
|
+
* suspect are just hidden by a changed server-side-filter), and hard-deleted
|
|
35
|
+
* rows are rows the page called `api.deleteRow` and confirmed it should be
|
|
36
|
+
* actively removed.
|
|
37
|
+
*/
|
|
38
|
+
removed: false | "soft" | "hard";
|
|
39
|
+
constructor(states: RowStates, row: GridDataRow<any>);
|
|
40
|
+
/**
|
|
41
|
+
* Whether we are effectively selected, for `GridTableApi.getSelectedRows`.
|
|
24
42
|
*
|
|
25
43
|
* Note that we don't use "I'm selected || my parent is selected" logic here, because whether a child is selected
|
|
26
44
|
* is actually based on whether it was _visible at the time the parent was selected_. So, we can't just assume
|
|
@@ -44,6 +62,9 @@ export declare class RowState {
|
|
|
44
62
|
* child of a selected parent row.
|
|
45
63
|
*/
|
|
46
64
|
select(selected: boolean): void;
|
|
65
|
+
/** Marks the row as removed from `props.rows`, to potentially become kept. */
|
|
66
|
+
markRemoved(): void;
|
|
67
|
+
toggleCollapsed(): void;
|
|
47
68
|
/** Whether this is a selected-but-filtered-out row that we should hoist to the top. */
|
|
48
69
|
get isKept(): boolean;
|
|
49
70
|
private get inferSelectedState();
|
|
@@ -13,22 +13,42 @@ class RowState {
|
|
|
13
13
|
// ...eventually...
|
|
14
14
|
// isDirectlyMatched = accept filters in the constructor and do match here
|
|
15
15
|
// isEffectiveMatched = isDirectlyMatched || hasMatchedChildren
|
|
16
|
-
constructor(
|
|
16
|
+
constructor(states, row) {
|
|
17
|
+
var _a;
|
|
17
18
|
/** Our children row states, as of the latest `props.rows`, without any filtering applied. */
|
|
18
19
|
this.children = undefined;
|
|
19
20
|
/** Whether we match a client-side filter; true if no filter is in place. */
|
|
20
21
|
this.isMatched = true;
|
|
21
22
|
/** Whether we are *directly* selected. */
|
|
22
23
|
this.selected = false;
|
|
23
|
-
/** Whether
|
|
24
|
-
this.
|
|
25
|
-
|
|
24
|
+
/** Whether we are collapsed. */
|
|
25
|
+
this.collapsed = false;
|
|
26
|
+
/**
|
|
27
|
+
* Whether our `row` had been in `props.rows`, but then removed _while being
|
|
28
|
+
* selected_, i.e. potentially by server-side filters.
|
|
29
|
+
*
|
|
30
|
+
* We have had a large foot-gun where users "select a row", change the filters,
|
|
31
|
+
* the row disappears (filtered out), and the user clicks "Go!", but the table
|
|
32
|
+
* thinks their previously-selected row is gone (b/c it's not in view), and
|
|
33
|
+
* then the row is inappropriately deleted/unassociated/etc. (b/c in the user's
|
|
34
|
+
* head, it is "still selected").
|
|
35
|
+
*
|
|
36
|
+
* To avoid this, we by default keep selected rows, as "kept rows", to make
|
|
37
|
+
* extra sure the user wants them to go away.
|
|
38
|
+
*
|
|
39
|
+
* Soft-deleted rows are rows that were removed from `props.rows` (i.e. we
|
|
40
|
+
* suspect are just hidden by a changed server-side-filter), and hard-deleted
|
|
41
|
+
* rows are rows the page called `api.deleteRow` and confirmed it should be
|
|
42
|
+
* actively removed.
|
|
43
|
+
*/
|
|
44
|
+
this.removed = false;
|
|
26
45
|
this.row = row;
|
|
27
46
|
this.selected = !!row.initSelected;
|
|
47
|
+
this.collapsed = (_a = states.storage.wasCollapsed(row.id)) !== null && _a !== void 0 ? _a : !!row.initCollapsed;
|
|
28
48
|
(0, mobx_1.makeAutoObservable)(this, { row: mobx_1.observable.ref });
|
|
29
49
|
}
|
|
30
50
|
/**
|
|
31
|
-
* Whether we are
|
|
51
|
+
* Whether we are effectively selected, for `GridTableApi.getSelectedRows`.
|
|
32
52
|
*
|
|
33
53
|
* Note that we don't use "I'm selected || my parent is selected" logic here, because whether a child is selected
|
|
34
54
|
* is actually based on whether it was _visible at the time the parent was selected_. So, we can't just assume
|
|
@@ -99,6 +119,16 @@ class RowState {
|
|
|
99
119
|
}
|
|
100
120
|
}
|
|
101
121
|
}
|
|
122
|
+
/** Marks the row as removed from `props.rows`, to potentially become kept. */
|
|
123
|
+
markRemoved() {
|
|
124
|
+
// The kept group is never in `props.rows`, so ignore asks to delete it
|
|
125
|
+
if (this.row.kind === src_1.KEPT_GROUP)
|
|
126
|
+
return;
|
|
127
|
+
this.removed = this.selected && this.removed !== "hard" ? "soft" : "hard";
|
|
128
|
+
}
|
|
129
|
+
toggleCollapsed() {
|
|
130
|
+
this.collapsed = !this.collapsed;
|
|
131
|
+
}
|
|
102
132
|
/** Whether this is a selected-but-filtered-out row that we should hoist to the top. */
|
|
103
133
|
get isKept() {
|
|
104
134
|
// this row is "kept" if it is selected, and:
|
|
@@ -108,7 +138,7 @@ class RowState {
|
|
|
108
138
|
// Headers, totals, etc., do not need keeping
|
|
109
139
|
!src_1.reservedRowKinds.includes(this.row.kind) &&
|
|
110
140
|
!this.isParent &&
|
|
111
|
-
(!this.isMatched || this.
|
|
141
|
+
(!this.isMatched || this.removed === "soft"));
|
|
112
142
|
}
|
|
113
143
|
get inferSelectedState() {
|
|
114
144
|
return this.row.inferSelectedState !== false;
|
|
@@ -118,7 +148,11 @@ class RowState {
|
|
|
118
148
|
// The keptGroup should treat all of its children as visible, as this makes select/unselect all work.
|
|
119
149
|
if (this.row.kind === src_1.KEPT_GROUP)
|
|
120
150
|
return (_a = this.children) !== null && _a !== void 0 ? _a : [];
|
|
121
|
-
|
|
151
|
+
// Ignore hard-deleted rows, i.e. from `api.deleteRows`; in theory any hard-deleted
|
|
152
|
+
// rows should be removed from `this.children` anyway, by a change to `props.rows`,
|
|
153
|
+
// but just in case the user calls _only_ `api.deleteRows`, and expects the row to
|
|
154
|
+
// go away, go ahead and filter them out here.
|
|
155
|
+
return (_c = (_b = this.children) === null || _b === void 0 ? void 0 : _b.filter((c) => c.isMatched === true && c.removed !== "hard")) !== null && _c !== void 0 ? _c : [];
|
|
122
156
|
}
|
|
123
157
|
/**
|
|
124
158
|
* Returns whether this row should act like a parent.
|