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
Uh oh!
There was an error while loading. Please reload this page.
-
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