Skip to content

Commit f6f9516

Browse files
authored
feat(data frame): Support basic cell styling (#1475)
1 parent 8e3ce87 commit f6f9516

File tree

19 files changed

+1085
-113
lines changed

19 files changed

+1085
-113
lines changed

js/data-frame/cell.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ColumnDef, RowModel, flexRender } from "@tanstack/react-table";
2-
import { VirtualItem } from "@tanstack/react-virtual";
32
import { Cell } from "@tanstack/table-core";
43
import React, {
54
FC,
@@ -15,6 +14,7 @@ import React, {
1514
import { CellEdit, SetCellEditMapAtLoc } from "./cell-edit-map";
1615
import { updateCellsData } from "./data-update";
1716
import { SelectionSet } from "./selection";
17+
import { CellStyle } from "./style-info";
1818
import type { PatchInfo } from "./types";
1919

2020
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -82,6 +82,7 @@ interface TableBodyCellProps {
8282
getSortedRowModel: () => RowModel<unknown[]>;
8383
setData: (fn: (draft: unknown[][]) => void) => void;
8484
cellEditInfo: CellEdit | undefined;
85+
cellStyle: CellStyle | undefined;
8586
setCellEditMapAtLoc: SetCellEditMapAtLoc;
8687
selection: SelectionSet<string, HTMLTableRowElement>;
8788
}
@@ -98,6 +99,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
9899
editCellsIsAllowed,
99100
getSortedRowModel,
100101
cellEditInfo,
102+
cellStyle,
101103
setData,
102104
setCellEditMapAtLoc,
103105
selection,
@@ -487,6 +489,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
487489
onDoubleClick={onCellDoubleClick}
488490
title={cellTitle}
489491
className={tableCellClass}
492+
style={{ ...cellStyle }}
490493
>
491494
{editContent}
492495
{content}

js/data-frame/index.tsx

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@ import {
1616
import { Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
1717
import React, {
1818
FC,
19-
ReactElement,
2019
StrictMode,
2120
useCallback,
2221
useEffect,
2322
useLayoutEffect,
2423
useMemo,
2524
useRef,
26-
useState,
2725
} from "react";
2826
import { Root, createRoot } from "react-dom/client";
2927
import { ErrorsMessageValue } from "rstudio-shiny/srcts/types/src/shiny/shinyapp";
@@ -36,12 +34,13 @@ import type { CellSelection, SelectionModesProp } from "./selection";
3634
import { SelectionModes, initSelectionModes, useSelection } from "./selection";
3735
import { SortingState, useSort } from "./sort";
3836
import { SortArrow } from "./sort-arrows";
37+
import { StyleInfo, getCellStyle, useStyleInfoMap } from "./style-info";
3938
import css from "./styles.scss";
4039
import { useTabindexGroup } from "./tabindex-group";
4140
import { useSummary } from "./table-summary";
42-
import { EditModeEnum, PandasData, PatchInfo, TypeHint } from "./types";
41+
import { PandasData, PatchInfo, TypeHint } from "./types";
4342

44-
// TODO-barret set selected cell as input! (Might be a followup?)
43+
// TODO-barret-future set selected cell as input! (Might be a followup?)
4544

4645
// TODO-barret; Type support
4746
// export interface PandasData<TIndex> {
@@ -109,7 +108,7 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
109108
const {
110109
columns,
111110
typeHints,
112-
data: rowData,
111+
data: tableDataProp,
113112
options: payloadOptions,
114113
} = payload;
115114
const { width, height, fill, filters: withFilters } = payloadOptions;
@@ -118,10 +117,42 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
118117
const theadRef = useRef<HTMLTableSectionElement>(null);
119118
const tbodyRef = useRef<HTMLTableSectionElement>(null);
120119

121-
const { cellEditMap, setCellEditMapAtLoc } = useCellEditMap();
122-
120+
const _useStyleInfo = useStyleInfoMap({
121+
initStyleInfos: payloadOptions["styles"],
122+
nrow: tableDataProp.length,
123+
ncol: columns.length,
124+
});
125+
/**
126+
* Contains all style information for the full table.
127+
*
128+
* Currently only the "data" location is supported.
129+
*/
130+
const styleInfoMap = _useStyleInfo.styleInfoMap;
131+
const { resetStyleInfos, setStyleInfos } = _useStyleInfo;
132+
133+
const _cellEditMap = useCellEditMap();
134+
/**
135+
* Contains all cell state and edit information
136+
*
137+
* If a cell's state is not in this map, it is assumed to be in the default display state.
138+
*/
139+
const cellEditMap = _cellEditMap.cellEditMap;
140+
/**
141+
* Set a cell's state or edit value in the `cellEditMap`
142+
*/
143+
const setCellEditMapAtLoc = _cellEditMap.setCellEditMapAtLoc;
144+
145+
/**
146+
* Determines if the user is allowed to edit cells in the table.
147+
*/
123148
const editCellsIsAllowed = payloadOptions["editable"] === true;
149+
("Barret");
124150

151+
/**
152+
* Determines if any cell is currently being edited
153+
*
154+
* This is currently being used to prevent row selection when a cell is being edited.
155+
*/
125156
const isEditingCell = useMemo<boolean>(() => {
126157
for (const cellEdit of cellEditMap.values()) {
127158
if (cellEdit.isEditing) {
@@ -131,6 +162,9 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
131162
return false;
132163
}, [cellEditMap]);
133164

165+
/**
166+
* Column definitions for the table
167+
*/
134168
const coldefs = useMemo<ColumnDef<unknown[], unknown>[]>(
135169
() =>
136170
columns.map((colname, colIndex) => {
@@ -179,16 +213,30 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
179213
// }
180214
// const [autoResetPageIndex, skipAutoResetPageIndex] = useSkipper();
181215

182-
const dataOriginal = useMemo(() => rowData, [rowData]);
183-
const [dataState, setData] = useImmer(rowData);
216+
/**
217+
* Copy of the original data
218+
*/
219+
const dataOriginal = useMemo(() => tableDataProp, [tableDataProp]);
220+
221+
const _tableData = useImmer(tableDataProp);
222+
/** Up-to-date data for the table */
223+
const tableData = _tableData[0];
224+
/** Function to update the data in the table */
225+
const setTableData = _tableData[1];
184226

185227
const getColDefs = (): ColumnDef<unknown[], unknown>[] => {
186228
return coldefs;
187229
};
188230

189-
const { sorting, sortState, sortingTableOptions, setSorting } = useSort({
190-
getColDefs,
191-
});
231+
const _sort = useSort({ getColDefs });
232+
/** Sorting state of the table */
233+
const sorting = _sort.sorting;
234+
/** Table options specific for sorting */
235+
const sortTableStateOptions = _sort.sortTableStateOptions;
236+
/** Sorting state of the table */
237+
const sortTableOptions = _sort.sortTableOptions;
238+
/** Set the sorting state of the table */
239+
const setSorting = _sort.setSorting;
192240

193241
const {
194242
columnFilters,
@@ -198,14 +246,14 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
198246
} = useFilters<unknown[]>(withFilters);
199247

200248
const options: TableOptions<unknown[]> = {
201-
data: dataState,
249+
data: tableData,
202250
columns: coldefs,
203251
state: {
204-
...sortState,
252+
...sortTableStateOptions,
205253
...columnFiltersState,
206254
},
207255
getCoreRowModel: getCoreRowModel(),
208-
...sortingTableOptions,
256+
...sortTableOptions,
209257
...filtersTableOptions,
210258
// debugAll: true,
211259
// Provide our updateCellsData function to our table meta
@@ -366,7 +414,7 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
366414
handleCellSelection as EventListener
367415
);
368416
};
369-
}, [id, selection, rowData]);
417+
}, [id, selection, tableData]);
370418

371419
useEffect(() => {
372420
const handleColumnSort = (
@@ -436,6 +484,28 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
436484
};
437485
}, [columns, id, setColumnFilters]);
438486

487+
useEffect(() => {
488+
const handleStyles = (event: CustomEvent<{ styles: StyleInfo[] }>) => {
489+
const styles = event.detail.styles;
490+
resetStyleInfos();
491+
setStyleInfos(styles);
492+
};
493+
494+
if (!id) return;
495+
496+
const element = document.getElementById(id);
497+
if (!element) return;
498+
499+
element.addEventListener("updateStyles", handleStyles as EventListener);
500+
501+
return () => {
502+
element.removeEventListener(
503+
"updateStyles",
504+
handleStyles as EventListener
505+
);
506+
};
507+
}, [setStyleInfos]);
508+
439509
useEffect(() => {
440510
if (!id) return;
441511
let shinyValue: CellSelection | null = null;
@@ -569,7 +639,7 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
569639
const headerRowCount = table.getHeaderGroups().length;
570640

571641
// Assume we're scrolling until proven otherwise
572-
let scrollingClass = rowData.length > 0 ? "scrolling" : "";
642+
let scrollingClass = tableData.length > 0 ? "scrolling" : "";
573643
const scrollHeight = containerRef.current?.scrollHeight;
574644
const clientHeight = containerRef.current?.clientHeight;
575645
if (scrollHeight && clientHeight && scrollHeight <= clientHeight) {
@@ -601,7 +671,7 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
601671
>
602672
<table
603673
className={tableClass + (withFilters ? " filtering" : "")}
604-
aria-rowcount={dataState.length}
674+
aria-rowcount={tableData.length}
605675
aria-multiselectable={canMultiRowSelect}
606676
style={{
607677
width: width === null || width === "auto" ? undefined : "100%",
@@ -693,6 +763,12 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
693763
rowIndex,
694764
columnIndex
695765
);
766+
const cellStyle = getCellStyle(
767+
styleInfoMap,
768+
"body",
769+
rowIndex,
770+
columnIndex
771+
);
696772

697773
return (
698774
<TableBodyCell
@@ -708,7 +784,8 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
708784
columnIndex={columnIndex}
709785
getSortedRowModel={table.getSortedRowModel}
710786
cellEditInfo={cellEditInfo}
711-
setData={setData}
787+
cellStyle={cellStyle}
788+
setData={setTableData}
712789
setCellEditMapAtLoc={setCellEditMapAtLoc}
713790
selection={selection}
714791
></TableBodyCell>

js/data-frame/sort.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ export function useSort<TData>({
1616
}): {
1717
sorting: SortingState;
1818
setSorting: React.Dispatch<React.SetStateAction<SortingState>>;
19-
sortState: { sorting: SortingState };
20-
sortingTableOptions: SortingOptions<TData>;
19+
sortTableStateOptions: { sorting: SortingState };
20+
sortTableOptions: SortingOptions<TData>;
2121
} {
2222
const [sorting, setSorting] = useState<SortingState>([]);
2323

2424
return {
2525
sorting,
26-
sortState: {
26+
sortTableStateOptions: {
2727
sorting,
2828
},
29-
sortingTableOptions: {
29+
sortTableOptions: {
3030
onSortingChange: (sortUpdater: Updater<SortingState>) => {
3131
const newSorting: SortingState =
3232
typeof sortUpdater === "function"

0 commit comments

Comments
 (0)