Skip to content

Commit eaf6985

Browse files
committed
Implement disabled rows and release version patch
1 parent e8c5549 commit eaf6985

27 files changed

+426
-597
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {
2+
InfiniteTable,
3+
DataSource,
4+
DataSourceData,
5+
type InfiniteTablePropColumns,
6+
} from '@infinite-table/infinite-react';
7+
8+
import * as React from 'react';
9+
import { useState } from 'react';
10+
11+
type Developer = {
12+
id: number;
13+
firstName: string;
14+
lastName: string;
15+
country: string;
16+
city: string;
17+
currency: string;
18+
19+
email: string;
20+
preferredLanguage: string;
21+
stack: string;
22+
canDesign: 'yes' | 'no';
23+
hobby: string;
24+
salary: number;
25+
age: number;
26+
};
27+
28+
const dataSource: DataSourceData<Developer> = ({}) => {
29+
return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers10-sql`)
30+
.then((r) => r.json())
31+
.then((data: Developer[]) => data);
32+
};
33+
34+
const columns: InfiniteTablePropColumns<Developer> = {
35+
preferredLanguage: { field: 'preferredLanguage' },
36+
id: { field: 'id' },
37+
country: { field: 'country' },
38+
salary: {
39+
field: 'salary',
40+
type: 'number',
41+
},
42+
age: { field: 'age' },
43+
canDesign: { field: 'canDesign' },
44+
firstName: { field: 'firstName' },
45+
stack: { field: 'stack' },
46+
47+
hobby: { field: 'hobby' },
48+
city: { field: 'city' },
49+
currency: { field: 'currency' },
50+
};
51+
52+
export default function KeyboardNavigationForRows() {
53+
const [activeRowIndex, setActiveRowIndex] = useState(0);
54+
55+
(globalThis as any).activeRowIndex = activeRowIndex;
56+
return (
57+
<DataSource<Developer>
58+
primaryKey="id"
59+
data={dataSource}
60+
selectionMode="multi-row"
61+
rowDisabledState={{
62+
enabledRows: true,
63+
disabledRows: [3, 5, 6],
64+
}}
65+
defaultRowSelection={{
66+
selectedRows: [5, 6, 7, 8],
67+
defaultSelection: false,
68+
}}
69+
>
70+
<InfiniteTable<Developer>
71+
columns={columns}
72+
activeRowIndex={activeRowIndex}
73+
onActiveRowIndexChange={setActiveRowIndex}
74+
keyboardNavigation="row"
75+
domProps={{
76+
autoFocus: true,
77+
style: {
78+
height: 800,
79+
},
80+
}}
81+
/>
82+
</DataSource>
83+
);
84+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { test, expect } from '@testing';
2+
3+
export default test.describe('Disabled rows', () => {
4+
test('should work fine', async ({ page, rowModel }) => {
5+
await page.waitForInfinite();
6+
7+
expect(await rowModel.isRowDisabled(0)).toBe(false);
8+
expect(await rowModel.isRowDisabled(3)).toBe(true);
9+
10+
await rowModel.clickRow(2);
11+
expect(await page.evaluate(() => (globalThis as any).activeRowIndex)).toBe(
12+
2,
13+
);
14+
15+
// clicking a disabled row should not change the active row
16+
await rowModel.clickRow(3);
17+
expect(await page.evaluate(() => (globalThis as any).activeRowIndex)).toBe(
18+
2,
19+
);
20+
21+
// arrow down should skip over disabled row
22+
await page.keyboard.press('ArrowDown');
23+
expect(await page.evaluate(() => (globalThis as any).activeRowIndex)).toBe(
24+
4,
25+
);
26+
27+
// again, arrow down should skip over disabled rows
28+
await page.keyboard.press('ArrowDown');
29+
expect(await page.evaluate(() => (globalThis as any).activeRowIndex)).toBe(
30+
7,
31+
);
32+
});
33+
});

examples/src/pages/tests/testUtils/RowTestingModel.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getSelectedRowIds,
1212
isNodeExpanded,
1313
isNodeGroupRow,
14+
isRowDisabled,
1415
toggleGroupRow,
1516
} from '.';
1617

@@ -87,6 +88,15 @@ export class RowTestingModel {
8788
return await isNodeGroupRow(node);
8889
}
8990

91+
async isRowDisabled(rowIndex: number) {
92+
const node = this.getCellLocator({
93+
rowIndex,
94+
colIndex: 0,
95+
});
96+
97+
return await isRowDisabled(node);
98+
}
99+
90100
async getCellComputedStylePropertyValue(
91101
cellLocation: CellLocation,
92102
propertyName: string,

examples/src/pages/tests/testUtils/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,18 @@ export const getCellNodeLocator = (
112112

113113
export const isNodeExpanded = async (node: Locator) => {
114114
return await node.evaluate((n) =>
115-
n.classList.contains('.InfiniteColumnCell--group-row-expanded'),
115+
n.classList.contains('InfiniteColumnCell--group-row-expanded'),
116116
);
117117
};
118118
export const isNodeGroupRow = async (node: Locator) => {
119119
return await node.evaluate((n) =>
120-
n.classList.contains('.InfiniteColumnCell--group-row'),
120+
n.classList.contains('InfiniteColumnCell--group-row'),
121+
);
122+
};
123+
124+
export const isRowDisabled = async (node: Locator) => {
125+
return await node.evaluate((n) =>
126+
n.classList.contains('InfiniteColumnCell--disabled'),
121127
);
122128
};
123129

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { BooleanCollectionState } from './BooleanCollectionState';
2+
import { RowDisabledStateObject } from './types';
3+
4+
export class RowDisabledState<KeyType = any> extends BooleanCollectionState<
5+
RowDisabledStateObject<KeyType>,
6+
KeyType
7+
> {
8+
constructor(
9+
state: RowDisabledStateObject<KeyType> | RowDisabledState<KeyType>,
10+
) {
11+
//@ts-ignore
12+
super(state);
13+
}
14+
public getState(): RowDisabledStateObject<KeyType> {
15+
const enabledRows = this.allPositive
16+
? true
17+
: [...(this.positiveMap?.keys() ?? [])];
18+
19+
const disabledRows = this.allNegative
20+
? true
21+
: [...(this.negativeMap?.keys() ?? [])];
22+
23+
return {
24+
enabledRows,
25+
disabledRows,
26+
} as RowDisabledStateObject<KeyType>;
27+
}
28+
29+
getPositiveFromState(state: RowDisabledStateObject<KeyType>) {
30+
return state.enabledRows;
31+
}
32+
getNegativeFromState(state: RowDisabledStateObject<KeyType>) {
33+
return state.disabledRows;
34+
}
35+
36+
public areAllDisabled() {
37+
return this.areAllNegative();
38+
}
39+
public areAllEnabled() {
40+
return this.areAllPositive();
41+
}
42+
43+
public disableAll() {
44+
this.makeAllNegative();
45+
}
46+
47+
public enableAll() {
48+
this.makeAllPositive();
49+
}
50+
51+
public isRowEnabled = (key: KeyType) => {
52+
return !!this.isItemPositive(key);
53+
};
54+
55+
public isRowDisabled(key: KeyType) {
56+
return !this.isRowEnabled(key);
57+
}
58+
59+
public setRowEnabled(key: KeyType, shouldExpand: boolean) {
60+
return this.setItemValue(key, shouldExpand);
61+
}
62+
63+
public disableRow(key: KeyType) {
64+
this.setRowEnabled(key, false);
65+
}
66+
public enableRow(key: KeyType) {
67+
this.setRowEnabled(key, true);
68+
}
69+
70+
public toggleRow(key: KeyType) {
71+
this.toggleItem(key);
72+
}
73+
}

source/src/components/DataSource/state/getInitialState.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
DataSourcePropOnCellSelectionChange_MultiCell,
77
DataSourcePropOnCellSelectionChange_SingleCell,
88
DataSourceRowInfoReducer,
9+
RowDisabledStateObject,
910
RowSelectionState,
1011
} from '..';
1112
import { dbg } from '../../../utils/debug';
@@ -47,6 +48,7 @@ import {
4748
} from '../types';
4849

4950
import { normalizeSortInfo } from './normalizeSortInfo';
51+
import { RowDisabledState } from '../RowDisabledState';
5052

5153
const DataSourceLogger = dbg('DataSource') as DebugLogger;
5254

@@ -101,6 +103,7 @@ export function initSetupState<T>(): DataSourceSetupState<T> {
101103

102104
propsCache: new Map<keyof DataSourceProps<T>, WeakMap<any, any>>([
103105
['sortInfo', new WeakMap()],
106+
['rowDisabledState', new WeakMap()],
104107
]),
105108

106109
rowInfoReducerResults: undefined,
@@ -202,6 +205,35 @@ export const forwardProps = <T>(
202205
},
203206
batchOperationDelay: 1,
204207
isRowSelected: 1,
208+
isRowDisabled: 1,
209+
rowDisabledState: (
210+
rowDisabledState:
211+
| RowDisabledState<T>
212+
| RowDisabledStateObject<T>
213+
| undefined,
214+
) => {
215+
if (!rowDisabledState) {
216+
return null;
217+
}
218+
if (rowDisabledState instanceof RowDisabledState) {
219+
return rowDisabledState;
220+
}
221+
222+
const wMap = setupState.propsCache.get('rowDisabledState') ?? weakMap;
223+
224+
let cachedRowDisabledState = wMap.get(
225+
rowDisabledState,
226+
) as RowDisabledState<T>;
227+
228+
if (!cachedRowDisabledState) {
229+
cachedRowDisabledState = new RowDisabledState<T>(rowDisabledState);
230+
wMap.set(rowDisabledState, cachedRowDisabledState);
231+
}
232+
233+
rowDisabledState = cachedRowDisabledState;
234+
235+
return rowDisabledState ?? null;
236+
},
205237
onDataArrayChange: 1,
206238
onDataMutations: 1,
207239
aggregationReducers: 1,
@@ -441,6 +473,22 @@ export function deriveStateFromProps<T extends any>(params: {
441473

442474
const pivotMode = shouldReloadData.pivotBy ? 'remote' : 'local';
443475

476+
const rowDisabledState = state.rowDisabledState;
477+
478+
let isRowDisabled = props.isRowDisabled;
479+
480+
if (!isRowDisabled && rowDisabledState) {
481+
const cachedIsRowDisabled = weakMap.get(rowDisabledState);
482+
if (cachedIsRowDisabled) {
483+
isRowDisabled = cachedIsRowDisabled;
484+
} else {
485+
isRowDisabled = (rowInfo) => {
486+
return rowDisabledState.isRowDisabled(rowInfo.id);
487+
};
488+
weakMap.set(rowDisabledState, isRowDisabled);
489+
}
490+
}
491+
444492
const result: DataSourceDerivedState<T> = {
445493
selectionMode,
446494
groupRowsState,
@@ -459,6 +507,7 @@ export function deriveStateFromProps<T extends any>(params: {
459507
groupBy: props.shouldReloadData?.groupBy ?? groupMode === 'remote',
460508
pivotBy: props.shouldReloadData?.pivotBy ?? pivotMode === 'remote',
461509
},
510+
isRowDisabled,
462511
rowSelection: rowSelectionState,
463512
cellSelection: cellSelectionState,
464513
groupMode,

source/src/components/DataSource/state/reducer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ function toRowInfo<T>(
8080
id: any,
8181
index: number,
8282
isRowSelected?: (rowInfo: InfiniteTableRowInfo<T>) => boolean | null,
83+
isRowDisabled?: (rowInfo: InfiniteTableRowInfo<T>) => boolean,
8384
cellSelectionState?: CellSelectionState,
8485
): InfiniteTable_NoGrouping_RowInfoNormal<T> {
8586
const rowInfo: InfiniteTable_NoGrouping_RowInfoNormal<T> = {
@@ -90,12 +91,16 @@ function toRowInfo<T>(
9091
isGroupRow: false,
9192
selfLoaded: true,
9293
rowSelected: false,
94+
rowDisabled: false,
9395
isCellSelected: returnFalse,
9496
hasSelectedCells: returnFalse,
9597
};
9698
if (isRowSelected) {
9799
rowInfo.rowSelected = isRowSelected(rowInfo);
98100
}
101+
if (isRowDisabled) {
102+
rowInfo.rowDisabled = isRowDisabled(rowInfo);
103+
}
99104

100105
if (cellSelectionState) {
101106
rowInfo.isCellSelected = (colId: string) => {
@@ -425,6 +430,7 @@ export function concludeReducer<T>(params: {
425430
}
426431
: undefined;
427432

433+
const isRowDisabled = state.isRowDisabled || returnFalse;
428434
if (state.isRowSelected && state.selectionMode === 'multi-row') {
429435
isRowSelected = (rowInfo) =>
430436
state.isRowSelected!(
@@ -588,6 +594,7 @@ export function concludeReducer<T>(params: {
588594
data ? toPrimaryKey(data) : index,
589595
index,
590596
isRowSelected,
597+
isRowDisabled,
591598
cellSelectionState,
592599
);
593600

0 commit comments

Comments
 (0)