Added selection / keyboard navigation
Summary: per title Reviewed By: nikoant Differential Revision: D26368673 fbshipit-source-id: 7a458e28af1229ee8193dfe2a6d156afd9282acd
This commit is contained in:
committed by
Facebook GitHub Bot
parent
fb7c09c972
commit
1ce665ceaf
@@ -69,9 +69,14 @@ type StateOptions = {
|
|||||||
|
|
||||||
export function createState<T>(
|
export function createState<T>(
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
|
options?: StateOptions,
|
||||||
|
): Atom<T>;
|
||||||
|
export function createState<T>(): Atom<T | undefined>;
|
||||||
|
export function createState(
|
||||||
|
initialValue: any = undefined,
|
||||||
options: StateOptions = {},
|
options: StateOptions = {},
|
||||||
): Atom<T> {
|
): Atom<any> {
|
||||||
const atom = new AtomValue<T>(initialValue);
|
const atom = new AtomValue(initialValue);
|
||||||
if (getCurrentPluginInstance() && options.persist) {
|
if (getCurrentPluginInstance() && options.persist) {
|
||||||
const {rootStates} = getCurrentPluginInstance()!;
|
const {rootStates} = getCurrentPluginInstance()!;
|
||||||
if (rootStates[options.persist]) {
|
if (rootStates[options.persist]) {
|
||||||
|
|||||||
@@ -337,8 +337,12 @@ export class DataSource<
|
|||||||
return this.reverse ? this.output.length - 1 - viewIndex : viewIndex;
|
return this.reverse ? this.output.length - 1 - viewIndex : viewIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem(viewIndex: number) {
|
getItem(viewIndex: number): T {
|
||||||
return this.output[this.normalizeIndex(viewIndex)].value;
|
return this.getEntry(viewIndex)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntry(viewIndex: number): Entry<T> {
|
||||||
|
return this.output[this.normalizeIndex(viewIndex)];
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyItemUpdated(viewIndex: number) {
|
notifyItemUpdated(viewIndex: number) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
|
MutableRefObject,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {DataSource} from '../../state/datasource/DataSource';
|
import {DataSource} from '../../state/datasource/DataSource';
|
||||||
import {useVirtual} from 'react-virtual';
|
import {useVirtual} from 'react-virtual';
|
||||||
@@ -28,6 +29,8 @@ enum UpdatePrio {
|
|||||||
HIGH,
|
HIGH,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DataSourceVirtualizer = ReturnType<typeof useVirtual>;
|
||||||
|
|
||||||
type DataSourceProps<T extends object, C> = {
|
type DataSourceProps<T extends object, C> = {
|
||||||
/**
|
/**
|
||||||
* The data source to render
|
* The data source to render
|
||||||
@@ -50,6 +53,8 @@ type DataSourceProps<T extends object, C> = {
|
|||||||
itemRenderer(item: T, index: number, context: C): React.ReactElement;
|
itemRenderer(item: T, index: number, context: C): React.ReactElement;
|
||||||
useFixedRowHeight: boolean;
|
useFixedRowHeight: boolean;
|
||||||
defaultRowHeight: number;
|
defaultRowHeight: number;
|
||||||
|
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
|
||||||
|
virtualizerRef?: MutableRefObject<DataSourceVirtualizer | undefined>;
|
||||||
_testHeight?: number; // exposed for unit testing only
|
_testHeight?: number; // exposed for unit testing only
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,6 +71,8 @@ export const DataSourceRenderer: <T extends object, C>(
|
|||||||
context,
|
context,
|
||||||
itemRenderer,
|
itemRenderer,
|
||||||
autoScroll,
|
autoScroll,
|
||||||
|
onKeyDown,
|
||||||
|
virtualizerRef,
|
||||||
_testHeight,
|
_testHeight,
|
||||||
}: DataSourceProps<any, any>) {
|
}: DataSourceProps<any, any>) {
|
||||||
/**
|
/**
|
||||||
@@ -89,6 +96,9 @@ export const DataSourceRenderer: <T extends object, C>(
|
|||||||
estimateSize: useCallback(() => defaultRowHeight, [forceHeightRecalculation.current, defaultRowHeight]),
|
estimateSize: useCallback(() => defaultRowHeight, [forceHeightRecalculation.current, defaultRowHeight]),
|
||||||
overscan: 0,
|
overscan: 0,
|
||||||
});
|
});
|
||||||
|
if (virtualizerRef) {
|
||||||
|
virtualizerRef.current = virtualizer;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
function subscribeToDataSource() {
|
function subscribeToDataSource() {
|
||||||
@@ -220,28 +230,30 @@ export const DataSourceRenderer: <T extends object, C>(
|
|||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<TableContainer onScroll={onScroll} ref={parentRef}>
|
<TableContainer onScroll={onScroll} ref={parentRef}>
|
||||||
<TableWindow height={virtualizer.totalSize}>
|
<TableWindow
|
||||||
{virtualizer.virtualItems.map((virtualRow) => (
|
height={virtualizer.totalSize}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
tabIndex={0}>
|
||||||
|
{virtualizer.virtualItems.map((virtualRow) => {
|
||||||
|
const entry = dataSource.getEntry(virtualRow.index);
|
||||||
// the position properties always change, so they are not part of the TableRow to avoid invalidating the memoized render always.
|
// the position properties always change, so they are not part of the TableRow to avoid invalidating the memoized render always.
|
||||||
// Also all row containers are renderd as part of same component to have 'less react' framework code in between*/}
|
// Also all row containers are renderd as part of same component to have 'less react' framework code in between*/}
|
||||||
<div
|
return (
|
||||||
key={virtualRow.index}
|
<div
|
||||||
style={{
|
key={virtualRow.index}
|
||||||
position: 'absolute',
|
style={{
|
||||||
top: 0,
|
position: 'absolute',
|
||||||
left: 0,
|
top: 0,
|
||||||
width: '100%',
|
left: 0,
|
||||||
height: useFixedRowHeight ? virtualRow.size : undefined,
|
width: '100%',
|
||||||
transform: `translateY(${virtualRow.start}px)`,
|
height: useFixedRowHeight ? virtualRow.size : undefined,
|
||||||
}}
|
transform: `translateY(${virtualRow.start}px)`,
|
||||||
ref={useFixedRowHeight ? undefined : virtualRow.measureRef}>
|
}}
|
||||||
{itemRenderer(
|
ref={useFixedRowHeight ? undefined : virtualRow.measureRef}>
|
||||||
dataSource.getItem(virtualRow.index),
|
{itemRenderer(entry.value, virtualRow.index, context)}
|
||||||
virtualRow.index,
|
</div>
|
||||||
context,
|
);
|
||||||
)}
|
})}
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</TableWindow>
|
</TableWindow>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,13 +7,20 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {MutableRefObject, RefObject, useMemo} from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
RefObject,
|
||||||
|
MutableRefObject,
|
||||||
|
} from 'react';
|
||||||
import {TableRow, DEFAULT_ROW_HEIGHT} from './TableRow';
|
import {TableRow, DEFAULT_ROW_HEIGHT} from './TableRow';
|
||||||
import {DataSource} from '../../state/datasource/DataSource';
|
import {DataSource} from '../../state/datasource/DataSource';
|
||||||
import {Layout} from '../Layout';
|
import {Layout} from '../Layout';
|
||||||
import {TableHead} from './TableHead';
|
import {TableHead} from './TableHead';
|
||||||
import {Percentage} from '../utils/widthUtils';
|
import {Percentage} from '../utils/widthUtils';
|
||||||
import {DataSourceRenderer} from './DataSourceRenderer';
|
import {DataSourceRenderer, DataSourceVirtualizer} from './DataSourceRenderer';
|
||||||
import {useDataTableManager, TableManager} from './useDataTableManager';
|
import {useDataTableManager, TableManager} from './useDataTableManager';
|
||||||
import {TableSearch} from './TableSearch';
|
import {TableSearch} from './TableSearch';
|
||||||
|
|
||||||
@@ -23,6 +30,15 @@ interface DataTableProps<T = any> {
|
|||||||
autoScroll?: boolean;
|
autoScroll?: boolean;
|
||||||
extraActions?: React.ReactElement;
|
extraActions?: React.ReactElement;
|
||||||
// custom onSearch(text, row) option?
|
// custom onSearch(text, row) option?
|
||||||
|
/**
|
||||||
|
* onSelect event
|
||||||
|
* @param item currently selected item
|
||||||
|
* @param index index of the selected item in the datasources' output.
|
||||||
|
* Note that the index could potentially refer to a different item if rendering is 'behind' and items have shifted
|
||||||
|
*/
|
||||||
|
onSelect?(item: T | undefined, index: number): void;
|
||||||
|
// multiselect?: true
|
||||||
|
// onMultiSelect
|
||||||
tableManagerRef?: RefObject<TableManager>;
|
tableManagerRef?: RefObject<TableManager>;
|
||||||
_testHeight?: number; // exposed for unit testing only
|
_testHeight?: number; // exposed for unit testing only
|
||||||
}
|
}
|
||||||
@@ -38,27 +54,113 @@ export type DataTableColumn<T = any> = {
|
|||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RenderingConfig<T = any> {
|
export interface RenderContext<T = any> {
|
||||||
columns: DataTableColumn<T>[];
|
columns: DataTableColumn<T>[];
|
||||||
|
onClick(item: T, itemId: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTable<T extends object>(props: DataTableProps<T>) {
|
export function DataTable<T extends object>(props: DataTableProps<T>) {
|
||||||
const tableManager = useDataTableManager<T>(props.dataSource, props.columns);
|
const {dataSource} = props;
|
||||||
|
const virtualizerRef = useRef<DataSourceVirtualizer | undefined>();
|
||||||
|
const tableManager = useDataTableManager<T>(
|
||||||
|
dataSource,
|
||||||
|
props.columns,
|
||||||
|
props.onSelect,
|
||||||
|
);
|
||||||
if (props.tableManagerRef) {
|
if (props.tableManagerRef) {
|
||||||
(props.tableManagerRef as MutableRefObject<TableManager>).current = tableManager;
|
(props.tableManagerRef as MutableRefObject<TableManager>).current = tableManager;
|
||||||
}
|
}
|
||||||
|
const {visibleColumns, selectItem, selection} = tableManager;
|
||||||
|
|
||||||
const renderingConfig = useMemo(() => {
|
const renderingConfig = useMemo<RenderContext<T>>(() => {
|
||||||
return {
|
return {
|
||||||
columns: tableManager.visibleColumns,
|
columns: visibleColumns,
|
||||||
|
onClick(_, itemIdx) {
|
||||||
|
selectItem(() => itemIdx);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, [tableManager.visibleColumns]);
|
}, [visibleColumns, selectItem]);
|
||||||
|
|
||||||
const usesWrapping = useMemo(
|
const usesWrapping = useMemo(
|
||||||
() => tableManager.columns.some((col) => col.wrap),
|
() => tableManager.columns.some((col) => col.wrap),
|
||||||
[tableManager.columns],
|
[tableManager.columns],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const itemRenderer = useCallback(
|
||||||
|
function itemRenderer(
|
||||||
|
item: any,
|
||||||
|
index: number,
|
||||||
|
renderContext: RenderContext<T>,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={index}
|
||||||
|
config={renderContext}
|
||||||
|
value={item}
|
||||||
|
itemIndex={index}
|
||||||
|
highlighted={index === tableManager.selection}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[tableManager.selection],
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard / selection handling
|
||||||
|
*/
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent<any>) => {
|
||||||
|
let handled = true;
|
||||||
|
switch (e.key) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
selectItem((idx) => (idx > 0 ? idx - 1 : 0));
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
selectItem((idx) =>
|
||||||
|
idx < dataSource.output.length - 1 ? idx + 1 : idx,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'Home':
|
||||||
|
selectItem(() => 0);
|
||||||
|
break;
|
||||||
|
case 'End':
|
||||||
|
selectItem(() => dataSource.output.length - 1);
|
||||||
|
break;
|
||||||
|
case 'PageDown':
|
||||||
|
selectItem((idx) =>
|
||||||
|
Math.min(
|
||||||
|
dataSource.output.length - 1,
|
||||||
|
idx + virtualizerRef.current!.virtualItems.length - 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'PageUp':
|
||||||
|
selectItem((idx) =>
|
||||||
|
Math.max(0, idx - virtualizerRef.current!.virtualItems.length - 1),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handled = false;
|
||||||
|
}
|
||||||
|
if (handled) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectItem, dataSource],
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
function scrollSelectionIntoView() {
|
||||||
|
if (selection >= 0) {
|
||||||
|
virtualizerRef.current?.scrollToIndex(selection, {
|
||||||
|
align: 'auto',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selection],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Top>
|
<Layout.Top>
|
||||||
<Layout.Container>
|
<Layout.Container>
|
||||||
@@ -76,30 +178,17 @@ export function DataTable<T extends object>(props: DataTableProps<T>) {
|
|||||||
onColumnSort={tableManager.sortColumn}
|
onColumnSort={tableManager.sortColumn}
|
||||||
/>
|
/>
|
||||||
</Layout.Container>
|
</Layout.Container>
|
||||||
<DataSourceRenderer<any, RenderContext>
|
<DataSourceRenderer<T, RenderContext<T>>
|
||||||
dataSource={props.dataSource}
|
dataSource={dataSource}
|
||||||
autoScroll={props.autoScroll}
|
autoScroll={props.autoScroll}
|
||||||
useFixedRowHeight={!usesWrapping}
|
useFixedRowHeight={!usesWrapping}
|
||||||
defaultRowHeight={DEFAULT_ROW_HEIGHT}
|
defaultRowHeight={DEFAULT_ROW_HEIGHT}
|
||||||
context={renderingConfig}
|
context={renderingConfig}
|
||||||
itemRenderer={itemRenderer}
|
itemRenderer={itemRenderer}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
virtualizerRef={virtualizerRef}
|
||||||
_testHeight={props._testHeight}
|
_testHeight={props._testHeight}
|
||||||
/>
|
/>
|
||||||
</Layout.Top>
|
</Layout.Top>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RenderContext = {
|
|
||||||
columns: DataTableColumn<any>[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function itemRenderer(item: any, index: number, renderContext: RenderContext) {
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={index}
|
|
||||||
config={renderContext}
|
|
||||||
row={item}
|
|
||||||
highlighted={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -70,18 +70,22 @@ const TableBodyColumnContainer = styled.div<{
|
|||||||
TableBodyColumnContainer.displayName = 'TableRow:TableBodyColumnContainer';
|
TableBodyColumnContainer.displayName = 'TableRow:TableBodyColumnContainer';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
config: RenderContext;
|
config: RenderContext<any>;
|
||||||
highlighted: boolean;
|
highlighted: boolean;
|
||||||
row: any;
|
value: any;
|
||||||
|
itemIndex: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TableRow = memo(function TableRow(props: Props) {
|
export const TableRow = memo(function TableRow(props: Props) {
|
||||||
const {config, highlighted, row} = props;
|
const {config, highlighted, value: row} = props;
|
||||||
return (
|
return (
|
||||||
<TableBodyRowContainer
|
<TableBodyRowContainer
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
data-key={row.key}
|
data-key={row.key}
|
||||||
className="ant-table-row">
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
props.config.onClick(props.value, props.itemIndex);
|
||||||
|
}}>
|
||||||
{config.columns
|
{config.columns
|
||||||
.filter((col) => col.visible)
|
.filter((col) => col.visible)
|
||||||
.map((col) => {
|
.map((col) => {
|
||||||
|
|||||||
@@ -49,21 +49,21 @@ test('update and append', async () => {
|
|||||||
const elem = await rendering.findAllByText('test DataTable');
|
const elem = await rendering.findAllByText('test DataTable');
|
||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
class="css-4f2ebr-TableBodyRowContainer efe0za01"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-table-row css-4f2ebr-TableBodyRowContainer efe0za01"
|
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
||||||
>
|
>
|
||||||
<div
|
test DataTable
|
||||||
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
|
||||||
>
|
|
||||||
test DataTable
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
|
||||||
>
|
|
||||||
true
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`);
|
<div
|
||||||
|
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
||||||
|
>
|
||||||
|
true
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
@@ -102,7 +102,7 @@ test('column visibility', async () => {
|
|||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
||||||
<div
|
<div
|
||||||
class="ant-table-row css-4f2ebr-TableBodyRowContainer efe0za01"
|
class="css-4f2ebr-TableBodyRowContainer efe0za01"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
||||||
@@ -127,7 +127,7 @@ test('column visibility', async () => {
|
|||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
||||||
<div
|
<div
|
||||||
class="ant-table-row css-4f2ebr-TableBodyRowContainer efe0za01"
|
class="css-4f2ebr-TableBodyRowContainer efe0za01"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
class="ant-table-cell css-1g4z4wd-TableBodyColumnContainer efe0za00"
|
||||||
|
|||||||
@@ -27,11 +27,14 @@ export type TableManager = ReturnType<typeof useDataTableManager>;
|
|||||||
export function useDataTableManager<T extends object>(
|
export function useDataTableManager<T extends object>(
|
||||||
dataSource: DataSource<T>,
|
dataSource: DataSource<T>,
|
||||||
defaultColumns: DataTableColumn<T>[],
|
defaultColumns: DataTableColumn<T>[],
|
||||||
|
onSelect?: (item: T | undefined, index: number) => void,
|
||||||
) {
|
) {
|
||||||
// TODO: restore from local storage
|
|
||||||
const [columns, setEffectiveColumns] = useState(
|
const [columns, setEffectiveColumns] = useState(
|
||||||
computeInitialColumns(defaultColumns),
|
computeInitialColumns(defaultColumns),
|
||||||
);
|
);
|
||||||
|
// TODO: move selection with shifts with index < selection?
|
||||||
|
// TODO: clear selection if out of range
|
||||||
|
const [selection, setSelection] = useState(-1);
|
||||||
const [sorting, setSorting] = useState<Sorting | undefined>(undefined);
|
const [sorting, setSorting] = useState<Sorting | undefined>(undefined);
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const visibleColumns = useMemo(
|
const visibleColumns = useMemo(
|
||||||
@@ -107,6 +110,21 @@ export function useDataTableManager<T extends object>(
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const selectItem = useCallback(
|
||||||
|
(updater: (currentIndex: number) => number) => {
|
||||||
|
setSelection((currentIndex) => {
|
||||||
|
const newIndex = updater(currentIndex);
|
||||||
|
const item =
|
||||||
|
newIndex >= 0 && newIndex < dataSource.output.length
|
||||||
|
? dataSource.getItem(newIndex)
|
||||||
|
: undefined;
|
||||||
|
onSelect?.(item, newIndex);
|
||||||
|
return newIndex;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSelection, onSelect, dataSource],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
function applyFilter() {
|
function applyFilter() {
|
||||||
dataSource.setFilter(currentFilter);
|
dataSource.setFilter(currentFilter);
|
||||||
@@ -131,6 +149,9 @@ export function useDataTableManager<T extends object>(
|
|||||||
toggleColumnVisibility,
|
toggleColumnVisibility,
|
||||||
/** Active search value */
|
/** Active search value */
|
||||||
setSearchValue,
|
setSearchValue,
|
||||||
|
/** current selection, describes the index index in the datasources's current output (not window) */
|
||||||
|
selection,
|
||||||
|
selectItem,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user