Add multiple types of selection #5985
Unanswered
Wombosvideo
asked this question in
Ideas
Replies: 1 comment
-
If you are looking for this yourself, you can just copy Below is the code from version 707a5b4: Code/**
* Copy of @tanstack/table-core RowSelection, renamed to RowHighlight.
*
* Adds the possibility to highlight rows without selecting them.
*
* Commit: @tanstack/table-core@707a5b4f67e93ada09168f39798015e45da6bf38
*/
import {
getMemoOptions,
makeStateUpdater,
memo,
type OnChangeFn,
type Row,
type RowData,
type RowModel,
type Table,
type TableFeature,
type Updater,
} from '@tanstack/table-core';
export type RowHighlightState = Record<string, boolean>;
export interface RowHighlightTableState {
rowHighlight: RowHighlightState;
}
export interface RowHighlightOptions<TData extends RowData> {
/**
* - Enables/disables multiple row highlighting for all rows in the table OR
* - A function that given a row, returns whether to enable/disable multiple row highlighting for that row's children/grandchildren
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#enablemultirowselection)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
enableMultiRowHighlight?: boolean | ((row: Row<TData>) => boolean);
/**
* - Enables/disables row highlighting for all rows in the table OR
* - A function that given a row, returns whether to enable/disable row highlighting for that row
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#enablerowselection)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
enableRowHighlight?: boolean | ((row: Row<TData>) => boolean);
/**
* Enables/disables automatic sub-row highlighting when a parent row is highlighted, or a function that enables/disables automatic sub-row highlighting for each row.
* (Use in combination with expanding or grouping features)
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#enablesubrowselection)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
enableSubRowHighlight?: boolean | ((row: Row<TData>) => boolean);
/**
* If provided, this function will be called with an `updaterFn` when `state.rowHighlight` changes. This overrides the default internal state management, so you will need to persist the state change either fully or partially outside of the table.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#onrowselectionchange)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
onRowHighlightChange?: OnChangeFn<RowHighlightState>;
// enableGroupingRowHighlight?:
// | boolean
// | ((
// row: Row<TData>
// ) => boolean)
// isAdditiveHighlightEvent?: (e: unknown) => boolean
// isInclusiveHighlightEvent?: (e: unknown) => boolean
// highlightRowsFn?: (
// table: Table<TData>,
// rowModel: RowModel<TData>
// ) => RowModel<TData>
}
export interface RowHighlightRow {
/**
* Returns whether or not the row can multi-highlight.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getcanmultiselect)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getCanMultiHighlight: () => boolean;
/**
* Returns whether or not the row can be highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getcanselect)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getCanHighlight: () => boolean;
/**
* Returns whether or not the row can highlight sub rows automatically when the parent row is highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getcanselectsubrows)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getCanHighlightSubRows: () => boolean;
/**
* Returns whether or not all of the row's sub rows are highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getisallsubrowsselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getIsAllSubRowsHighlighted: () => boolean;
/**
* Returns whether or not the row is highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getisselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getIsHighlighted: () => boolean;
/**
* Returns whether or not some of the row's sub rows are highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getissomeselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getIsSomeHighlighted: () => boolean;
/**
* Returns a handler that can be used to toggle the row.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#gettoggleselectedhandler)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getToggleHighlightedHandler: () => (event: unknown) => void;
/**
* Highlights/unhighlights the row.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#toggleselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
toggleHighlighted: (value?: boolean, opts?: { selectChildren?: boolean }) => void;
}
export interface RowHighlightInstance<TData extends RowData> {
/**
* Returns the row model of all rows that are highlighted after filtering has been applied.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getfilteredselectedrowmodel)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getFilteredHighlightedRowModel: () => RowModel<TData>;
/**
* Returns the row model of all rows that are highlighted after grouping has been applied.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getgroupedselectedrowmodel)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getGroupedHighlightedRowModel: () => RowModel<TData>;
/**
* Returns whether or not all rows on the current page are highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getisallpagerowsselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getIsAllPageRowsHighlighted: () => boolean;
/**
* Returns whether or not all rows in the table are highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getisallrowsselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getIsAllRowsHighlighted: () => boolean;
/**
* Returns whether or not any rows on the current page are highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getissomepagerowsselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getIsSomePageRowsHighlighted: () => boolean;
/**
* Returns whether or not any rows in the table are highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getissomerowsselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getIsSomeRowsHighlighted: () => boolean;
/**
* Returns the core row model of all rows before row highlighting has been applied.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getpreselectedrowmodel)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getPreHighlightedRowModel: () => RowModel<TData>;
/**
* Returns the row model of all rows that are highlighted.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#getselectedrowmodel)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getHighlightedRowModel: () => RowModel<TData>;
/**
* Returns a handler that can be used to toggle all rows on the current page.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#gettoggleallpagerowsselectedhandler)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getToggleAllPageRowsHighlightedHandler: () => (event: unknown) => void;
/**
* Returns a handler that can be used to toggle all rows in the table.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#gettoggleallrowsselectedhandler)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
getToggleAllRowsHighlightedHandler: () => (event: unknown) => void;
/**
* Resets the **rowHighlight** state to the `initialState.rowHighlight`, or `true` can be passed to force a default blank state reset to `{}`.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#resetrowselection)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
resetRowHighlight: (defaultState?: boolean) => void;
/**
* Sets or updates the `state.rowHighlight` state.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#setrowselection)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
setRowHighlight: (updater: Updater<RowHighlightState>) => void;
/**
* Highlights/unhighlights all rows on the current page.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#toggleallpagerowsselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
toggleAllPageRowsHighlighted: (value?: boolean) => void;
/**
* Highlights/unhighlights all rows in the table.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-selection#toggleallrowsselected)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
toggleAllRowsHighlighted: (value?: boolean) => void;
}
declare module '@tanstack/table-core' {
/* eslint-disable @typescript-eslint/no-empty-object-type */
interface TableState extends RowHighlightTableState {}
interface TableOptionsResolved<TData extends RowData> extends RowHighlightOptions<TData> {}
interface Table<TData extends RowData> extends RowHighlightInstance<TData> {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Row<TData extends RowData> extends RowHighlightRow {}
/* eslint-enable @typescript-eslint/no-empty-object-type */
}
//
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const RowHighlight: TableFeature<any> = {
getInitialState: (state): RowHighlightTableState => {
return {
rowHighlight: {},
...state,
};
},
getDefaultOptions: <TData extends RowData>(table: Table<TData>): RowHighlightOptions<TData> => {
return {
onRowHighlightChange: makeStateUpdater('rowHighlight', table),
enableRowHighlight: true,
enableMultiRowHighlight: true,
enableSubRowHighlight: true,
// enableGroupingRowHighlight: false,
// isAdditiveHighlightEvent: (e: unknown) => !!e.metaKey,
// isInclusiveHighlightEvent: (e: unknown) => !!e.shiftKey,
};
},
createTable: <TData extends RowData>(table: Table<TData>): void => {
table.setRowHighlight = (updater) => table.options.onRowHighlightChange?.(updater);
table.resetRowHighlight = (defaultState) =>
table.setRowHighlight(defaultState ? {} : (table.initialState.rowHighlight ?? {}));
table.toggleAllRowsHighlighted = (value) => {
table.setRowHighlight((old) => {
value = typeof value !== 'undefined' ? value : !table.getIsAllRowsHighlighted();
const rowHighlight = { ...old };
const preGroupedFlatRows = table.getPreGroupedRowModel().flatRows;
// We don't use `mutateRowIsHighlighted` here for performance reasons.
// All of the rows are flat already, so it wouldn't be worth it
if (value) {
preGroupedFlatRows.forEach((row) => {
if (!row.getCanHighlight()) {
return;
}
rowHighlight[row.id] = true;
});
} else {
preGroupedFlatRows.forEach((row) => {
delete rowHighlight[row.id];
});
}
return rowHighlight;
});
};
table.toggleAllPageRowsHighlighted = (value) =>
table.setRowHighlight((old) => {
const resolvedValue =
typeof value !== 'undefined' ? value : !table.getIsAllPageRowsHighlighted();
const rowHighlight: RowHighlightState = { ...old };
table.getRowModel().rows.forEach((row) => {
mutateRowIsHighlighted(rowHighlight, row.id, resolvedValue, true, table);
});
return rowHighlight;
});
// addRowHighlightRange: rowId => {
// const {
// rows,
// rowsById,
// options: { selectGroupingRows, selectSubRows },
// } = table
// const findHighlightedRow = (rows: Row[]) => {
// let found
// rows.find(d => {
// if (d.getIsHighlighted()) {
// found = d
// return true
// }
// const subFound = findHighlightedRow(d.subRows || [])
// if (subFound) {
// found = subFound
// return true
// }
// return false
// })
// return found
// }
// const firstRow = findHighlightedRow(rows) || rows[0]
// const lastRow = rowsById[rowId]
// let include = false
// const selectedRowIds = {}
// const addRow = (row: Row) => {
// mutateRowIsHighlighted(selectedRowIds, row.id, true, {
// rowsById,
// selectGroupingRows: selectGroupingRows!,
// selectSubRows: selectSubRows!,
// })
// }
// table.rows.forEach(row => {
// const isFirstRow = row.id === firstRow.id
// const isLastRow = row.id === lastRow.id
// if (isFirstRow || isLastRow) {
// if (!include) {
// include = true
// } else if (include) {
// addRow(row)
// include = false
// }
// }
// if (include) {
// addRow(row)
// }
// })
// table.setRowHighlight(selectedRowIds)
// },
table.getPreHighlightedRowModel = () => table.getCoreRowModel();
table.getHighlightedRowModel = memo(
() => [table.getState().rowHighlight, table.getCoreRowModel()],
(rowHighlight, rowModel) => {
if (!Object.keys(rowHighlight).length) {
return {
rows: [],
flatRows: [],
rowsById: {},
};
}
return highlightRowsFn(table, rowModel);
},
getMemoOptions(table.options, 'debugTable', 'getHighlightedRowModel'),
);
table.getFilteredHighlightedRowModel = memo(
() => [table.getState().rowHighlight, table.getFilteredRowModel()],
(rowHighlight, rowModel) => {
if (!Object.keys(rowHighlight).length) {
return {
rows: [],
flatRows: [],
rowsById: {},
};
}
return highlightRowsFn(table, rowModel);
},
getMemoOptions(table.options, 'debugTable', 'getFilteredHighlightedRowModel'),
);
table.getGroupedHighlightedRowModel = memo(
() => [table.getState().rowHighlight, table.getSortedRowModel()],
(rowHighlight, rowModel) => {
if (!Object.keys(rowHighlight).length) {
return {
rows: [],
flatRows: [],
rowsById: {},
};
}
return highlightRowsFn(table, rowModel);
},
getMemoOptions(table.options, 'debugTable', 'getGroupedHighlightedRowModel'),
);
///
// getGroupingRowCanHighlight: rowId => {
// const row = table.getRow(rowId)
// if (!row) {
// throw new Error()
// }
// if (typeof table.options.enableGroupingRowHighlight === 'function') {
// return table.options.enableGroupingRowHighlight(row)
// }
// return table.options.enableGroupingRowHighlight ?? false
// },
table.getIsAllRowsHighlighted = () => {
const preGroupedFlatRows = table.getFilteredRowModel().flatRows;
const { rowHighlight } = table.getState();
let isAllRowsHighlighted = Boolean(
preGroupedFlatRows.length && Object.keys(rowHighlight).length,
);
if (isAllRowsHighlighted) {
if (preGroupedFlatRows.some((row) => row.getCanHighlight() && !rowHighlight[row.id])) {
isAllRowsHighlighted = false;
}
}
return isAllRowsHighlighted;
};
table.getIsAllPageRowsHighlighted = () => {
const paginationFlatRows = table
.getPaginationRowModel()
.flatRows.filter((row) => row.getCanHighlight());
const { rowHighlight } = table.getState();
let isAllPageRowsHighlighted = !!paginationFlatRows.length;
if (isAllPageRowsHighlighted && paginationFlatRows.some((row) => !rowHighlight[row.id])) {
isAllPageRowsHighlighted = false;
}
return isAllPageRowsHighlighted;
};
table.getIsSomeRowsHighlighted = () => {
const totalHighlighted = Object.keys(table.getState().rowHighlight ?? {}).length;
return totalHighlighted > 0 && totalHighlighted < table.getFilteredRowModel().flatRows.length;
};
table.getIsSomePageRowsHighlighted = () => {
const paginationFlatRows = table.getPaginationRowModel().flatRows;
return table.getIsAllPageRowsHighlighted()
? false
: paginationFlatRows
.filter((row) => row.getCanHighlight())
.some((d) => d.getIsHighlighted() || d.getIsSomeHighlighted());
};
table.getToggleAllRowsHighlightedHandler = () => {
return (e: unknown) => {
table.toggleAllRowsHighlighted(((e as MouseEvent).target as HTMLInputElement).checked);
};
};
table.getToggleAllPageRowsHighlightedHandler = () => {
return (e: unknown) => {
table.toggleAllPageRowsHighlighted(((e as MouseEvent).target as HTMLInputElement).checked);
};
};
},
createRow: <TData extends RowData>(row: Row<TData>, table: Table<TData>): void => {
row.toggleHighlighted = (value, opts) => {
const isHighlighted = row.getIsHighlighted();
table.setRowHighlight((old) => {
value = typeof value !== 'undefined' ? value : !isHighlighted;
if (row.getCanHighlight() && isHighlighted === value) {
return old;
}
const selectedRowIds = { ...old };
mutateRowIsHighlighted(selectedRowIds, row.id, value, opts?.selectChildren ?? true, table);
return selectedRowIds;
});
};
row.getIsHighlighted = () => {
const { rowHighlight } = table.getState();
return isRowHighlighted(row, rowHighlight);
};
row.getIsSomeHighlighted = () => {
const { rowHighlight } = table.getState();
return isSubRowHighlighted(row, rowHighlight, table) === 'some';
};
row.getIsAllSubRowsHighlighted = () => {
const { rowHighlight } = table.getState();
return isSubRowHighlighted(row, rowHighlight, table) === 'all';
};
row.getCanHighlight = () => {
if (typeof table.options.enableRowHighlight === 'function') {
return table.options.enableRowHighlight(row);
}
return table.options.enableRowHighlight ?? true;
};
row.getCanHighlightSubRows = () => {
if (typeof table.options.enableSubRowHighlight === 'function') {
return table.options.enableSubRowHighlight(row);
}
return table.options.enableSubRowHighlight ?? true;
};
row.getCanMultiHighlight = () => {
if (typeof table.options.enableMultiRowHighlight === 'function') {
return table.options.enableMultiRowHighlight(row);
}
return table.options.enableMultiRowHighlight ?? true;
};
row.getToggleHighlightedHandler = () => {
const canHighlight = row.getCanHighlight();
return (e: unknown) => {
if (!canHighlight) return;
row.toggleHighlighted(((e as MouseEvent).target as HTMLInputElement)?.checked);
};
};
},
};
const mutateRowIsHighlighted = <TData extends RowData>(
highlightedRowIds: Record<string, boolean>,
id: string,
value: boolean,
includeChildren: boolean,
table: Table<TData>,
) => {
const row = table.getRow(id, true);
// const isGrouped = row.getIsGrouped()
// if ( // TODO: enforce grouping row selection rules
// !isGrouped ||
// (isGrouped && table.options.enableGroupingRowHighlight)
// ) {
if (value) {
if (!row.getCanMultiHighlight()) {
Object.keys(highlightedRowIds).forEach((key) => delete highlightedRowIds[key]);
}
if (row.getCanHighlight()) {
highlightedRowIds[id] = true;
}
} else {
delete highlightedRowIds[id];
}
// }
if (includeChildren && row.subRows?.length && row.getCanHighlightSubRows()) {
row.subRows.forEach((row) =>
mutateRowIsHighlighted(highlightedRowIds, row.id, value, includeChildren, table),
);
}
};
export function highlightRowsFn<TData extends RowData>(
table: Table<TData>,
rowModel: RowModel<TData>,
): RowModel<TData> {
const rowHighlight = table.getState().rowHighlight;
const newHighlightedFlatRows: Row<TData>[] = [];
const newHighlightedRowsById: Record<string, Row<TData>> = {};
// Filters top level and nested rows
const recurseRows = (rows: Row<TData>[], depth = 0): Row<TData>[] => {
return rows
.map((row) => {
const isHighlighted = isRowHighlighted(row, rowHighlight);
if (isHighlighted) {
newHighlightedFlatRows.push(row);
newHighlightedRowsById[row.id] = row;
}
if (row.subRows?.length) {
row = {
...row,
subRows: recurseRows(row.subRows, depth + 1),
};
}
if (isHighlighted) {
return row;
}
})
.filter(Boolean) as Row<TData>[];
};
return {
rows: recurseRows(rowModel.rows),
flatRows: newHighlightedFlatRows,
rowsById: newHighlightedRowsById,
};
}
export function isRowHighlighted<TData extends RowData>(
row: Row<TData>,
highlighted: Record<string, boolean>,
): boolean {
return highlighted[row.id] ?? false;
}
export function isSubRowHighlighted<TData extends RowData>(
row: Row<TData>,
highlighted: Record<string, boolean>,
table: Table<TData>,
): boolean | 'some' | 'all' {
if (!row.subRows?.length) return false;
let allChildrenHighlighted = true;
let someHighlighted = false;
row.subRows.forEach((subRow) => {
// Bail out early if we know both of these
if (someHighlighted && !allChildrenHighlighted) {
return;
}
if (subRow.getCanHighlight()) {
if (isRowHighlighted(subRow, highlighted)) {
someHighlighted = true;
} else {
allChildrenHighlighted = false;
}
}
// Check row selection of nested subrows
if (subRow.subRows && subRow.subRows.length) {
const subRowChildrenHighlighted = isSubRowHighlighted(subRow, highlighted, table);
if (subRowChildrenHighlighted === 'all') {
someHighlighted = true;
} else if (subRowChildrenHighlighted === 'some') {
someHighlighted = true;
allChildrenHighlighted = false;
} else {
allChildrenHighlighted = false;
}
}
});
return allChildrenHighlighted ? 'all' : someHighlighted ? 'some' : false;
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I want to implement multiple type of selection for a single table.
This is useful in a scenario where you want to preview row data on click (like files) and have the clicked row highlighted but not select the rows for a future action (like deleting the selected files).
There may be other scenarios where it's useful to have even more types of selection and highlight the corresponding rows in a different color for example.
Beta Was this translation helpful? Give feedback.
All reactions