diff --git a/packages/core/src/data-editor/data-editor.tsx b/packages/core/src/data-editor/data-editor.tsx index de75d8bac..98a7a6e03 100644 --- a/packages/core/src/data-editor/data-editor.tsx +++ b/packages/core/src/data-editor/data-editor.tsx @@ -499,7 +499,7 @@ export interface DataEditorProps extends Props, Pick; + + /** + * When true (default) draws accent-coloured grid lines around selected columns in the header. Set to false to + * revert to the original behaviour where only the header background is accented. + * @group Style + * @defaultValue true + */ + readonly columnSelectionGridLines?: boolean; + + /** + * When true (default) draws accent-coloured grid lines around selected rows in the header. Set to false to + * revert to the original behaviour where only the header background is accented. + * @group Style + * @defaultValue true + */ + readonly rowSelectionGridLines?: boolean; } type ScrollToFn = ( @@ -892,6 +908,8 @@ const DataEditorImpl: React.ForwardRefRenderFunction {renameGroupNode} {overlay !== undefined && ( diff --git a/packages/core/src/docs/examples/selection-gridlines.stories.tsx b/packages/core/src/docs/examples/selection-gridlines.stories.tsx new file mode 100644 index 000000000..db7a0a84d --- /dev/null +++ b/packages/core/src/docs/examples/selection-gridlines.stories.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { DataEditorAll as DataEditor } from "../../data-editor-all.js"; +import { + BeautifulWrapper, + Description, + MoreInfo, + useMockDataGenerator, + defaultProps, +} from "../../data-editor/stories/utils.js"; +import { SimpleThemeWrapper } from "../../stories/story-utils.js"; + +export default { + title: "Glide-Data-Grid/DataEditor Demos", + + decorators: [ + (Story: React.ComponentType) => ( + + + + Demonstrates the columnSelectionGridLines and + rowSelectionGridLines props which control whether accent-coloured grid + lines are drawn around selected columns and rows. + + + Use the story controls to toggle the behaviours on and off. + + + }> + + + + ), + ], +}; + +interface SelectionGridLineProps { + columnSelectionGridLines: boolean; + rowSelectionGridLines: boolean; +} + +export const SelectionGridLine: React.FC = p => { + const { cols, getCellContent } = useMockDataGenerator(10); + return ( + + ); +}; + +(SelectionGridLine as any).args = { + columnSelectionGridLines: true, + rowSelectionGridLines: true, +}; + +(SelectionGridLine as any).argTypes = { + columnSelectionGridLines: { + control: { + type: "boolean", + }, + }, + rowSelectionGridLines: { + control: { + type: "boolean", + }, + }, +}; \ No newline at end of file diff --git a/packages/core/src/internal/data-grid-dnd/data-grid-dnd.tsx b/packages/core/src/internal/data-grid-dnd/data-grid-dnd.tsx index b2950330f..7951ff8ec 100644 --- a/packages/core/src/internal/data-grid-dnd/data-grid-dnd.tsx +++ b/packages/core/src/internal/data-grid-dnd/data-grid-dnd.tsx @@ -439,6 +439,8 @@ const DataGridDnd: React.FunctionComponent = p => { dragAndDropState={dragOffset} onMouseMoveRaw={onMouseMove} ref={gridRef} + columnSelectionGridLines={p.columnSelectionGridLines} + rowSelectionGridLines={p.rowSelectionGridLines} /> ); }; diff --git a/packages/core/src/internal/data-grid-search/data-grid-search.tsx b/packages/core/src/internal/data-grid-search/data-grid-search.tsx index 0f0bb2486..5a3c6772c 100644 --- a/packages/core/src/internal/data-grid-search/data-grid-search.tsx +++ b/packages/core/src/internal/data-grid-search/data-grid-search.tsx @@ -549,6 +549,8 @@ const DataGridSearch: React.FunctionComponent = p => { smoothScrollX={p.smoothScrollX} smoothScrollY={p.smoothScrollY} resizeIndicator={p.resizeIndicator} + columnSelectionGridLines={p.columnSelectionGridLines} + rowSelectionGridLines={p.rowSelectionGridLines} /> {searchbox} diff --git a/packages/core/src/internal/data-grid/data-grid.tsx b/packages/core/src/internal/data-grid/data-grid.tsx index 9f942e352..d53c571de 100644 --- a/packages/core/src/internal/data-grid/data-grid.tsx +++ b/packages/core/src/internal/data-grid/data-grid.tsx @@ -307,6 +307,18 @@ export interface DataGridProps { * @group Style */ readonly resizeIndicator: "full" | "header" | "none" | undefined; + + /** Enables accent-coloured grid lines for selected columns in the header. + * @defaultValue false + * @group Style + */ + readonly columnSelectionGridLines?: boolean; + + /** Enables accent-coloured grid lines for selected rows in the header. + * @defaultValue false + * @group Style + */ + readonly rowSelectionGridLines?: boolean; } type DamageUpdateList = readonly { @@ -320,7 +332,11 @@ export interface DataGridRef { focus: () => void; getBounds: (col?: number, row?: number) => Rectangle | undefined; damage: (cells: DamageUpdateList) => void; - getMouseArgsForPosition: (posX: number, posY: number, ev?: MouseEvent | TouchEvent) => GridMouseEventArgs | undefined; + getMouseArgsForPosition: ( + posX: number, + posY: number, + ev?: MouseEvent | TouchEvent + ) => GridMouseEventArgs | undefined; } const getRowData = (cell: InnerGridCell, getCellRenderer?: GetCellRendererCallback) => { @@ -396,6 +412,8 @@ const DataGrid: React.ForwardRefRenderFunction = (p, experimental, getCellRenderer, resizeIndicator = "full", + columnSelectionGridLines = false, + rowSelectionGridLines = false, } = p; const translateX = p.translateX ?? 0; const translateY = p.translateY ?? 0; @@ -446,7 +464,10 @@ const DataGrid: React.ForwardRefRenderFunction = (p, }, [cellYOffset, cellXOffset, translateX, translateY, enableFirefoxRescaling, enableSafariRescaling]); const mappedColumns = useMappedColumns(columns, freezeColumns); - const stickyX = React.useMemo(() => fixedShadowX ? getStickyWidth(mappedColumns, dragAndDropState) : 0,[mappedColumns, dragAndDropState, fixedShadowX]); + const stickyX = React.useMemo( + () => (fixedShadowX ? getStickyWidth(mappedColumns, dragAndDropState) : 0), + [mappedColumns, dragAndDropState, fixedShadowX] + ); // row: -1 === columnHeader, -2 === groupHeader const getBoundsForItem = React.useCallback( @@ -829,6 +850,8 @@ const DataGrid: React.ForwardRefRenderFunction = (p, getCellRenderer, minimumCellWidth, resizeIndicator, + columnSelectionGridLines, + rowSelectionGridLines, }; // This confusing bit of code due to some poor design. Long story short, the damage property is only used @@ -895,6 +918,8 @@ const DataGrid: React.ForwardRefRenderFunction = (p, getCellRenderer, minimumCellWidth, resizeIndicator, + columnSelectionGridLines, + rowSelectionGridLines, ]); const lastDrawRef = React.useRef(draw); @@ -951,15 +976,15 @@ const DataGrid: React.ForwardRefRenderFunction = (p, const cursor = isDragging ? "grabbing" : canDrag || isResizing - ? "col-resize" - : overFill || isFilling - ? "crosshair" - : cursorOverride !== undefined - ? cursorOverride - : headerHovered || clickableInnerCellHovered || editableBoolHovered || groupHeaderHovered - ? "pointer" - : "default"; - + ? "col-resize" + : overFill || isFilling + ? "crosshair" + : cursorOverride !== undefined + ? cursorOverride + : headerHovered || clickableInnerCellHovered || editableBoolHovered || groupHeaderHovered + ? "pointer" + : "default"; + const style = React.useMemo( () => ({ // width, @@ -1716,7 +1741,7 @@ const DataGrid: React.ForwardRefRenderFunction = (p, } return getMouseArgsForPosition(canvasRef.current, posX, posY, ev); - } + }, }), [canvasRef, damage, getBoundsForItem] ); diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts index cd4b4cf8d..948bcfb2b 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.lines.ts @@ -294,7 +294,9 @@ export function drawGridLines( freezeTrailingRows: number, rows: number, theme: FullTheme, - verticalOnly: boolean = false + verticalOnly: boolean = false, + selectedColumns?: CompactSelection, + selectedRows?: CompactSelection ) { if (spans !== undefined) { ctx.beginPath(); @@ -307,6 +309,8 @@ export function drawGridLines( } const hColor = theme.horizontalBorderColor ?? theme.borderColor; const vColor = theme.borderColor; + const selectedVColor = theme.accentColor; + const selectedHColor = theme.accentColor; const { minX, maxX, minY, maxY } = getMinMaxXY(drawRegions, width, height); @@ -322,12 +326,20 @@ export function drawGridLines( x += c.width; const tx = c.sticky ? x : x + translateX; if (tx >= minX && tx <= maxX && verticalBorder(index + 1)) { + const leftSelected = selectedColumns?.hasIndex(c.sourceIndex) ?? false; + const rightSelected = + index < effectiveCols.length - 1 + ? selectedColumns?.hasIndex(effectiveCols[index + 1].sourceIndex) ?? false + : false; + const color = leftSelected !== rightSelected + ? selectedVColor + : vColor; toDraw.push({ x1: tx, y1: Math.max(groupHeaderHeight, minY), x2: tx, y2: Math.min(height, maxY), - color: vColor, + color, }); } } @@ -348,12 +360,25 @@ export function drawGridLines( const ty = y + translateY; if (ty >= minY && ty <= maxY - 1) { const rowTheme = getRowThemeOverride?.(row); + + let color = rowTheme?.horizontalBorderColor ?? rowTheme?.borderColor ?? hColor; + + if (selectedRows !== undefined) { + const currentSelected = selectedRows.hasIndex(row); + const prevSelected = row > 0 ? selectedRows.hasIndex(row - 1) : false; + + // Accent the line if it is a boundary between selected and unselected rows. + if (currentSelected !== prevSelected && (currentSelected || prevSelected)) { + color = selectedHColor; + } + } + toDraw.push({ x1: minX, y1: ty, x2: maxX, y2: ty, - color: rowTheme?.horizontalBorderColor ?? rowTheme?.borderColor ?? hColor, + color, }); } diff --git a/packages/core/src/internal/data-grid/render/data-grid-render.ts b/packages/core/src/internal/data-grid/render/data-grid-render.ts index 4946b1557..8ed500308 100644 --- a/packages/core/src/internal/data-grid/render/data-grid-render.ts +++ b/packages/core/src/internal/data-grid/render/data-grid-render.ts @@ -268,6 +268,17 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { } } const drawHeaderTexture = () => { + + // Draw the bottom border of the header. + overlayCtx.beginPath(); + overlayCtx.moveTo(0, overlayHeight - 0.5); + overlayCtx.lineTo(width, overlayHeight - 0.5); + overlayCtx.strokeStyle = blend( + theme.headerBottomBorderColor ?? theme.horizontalBorderColor ?? theme.borderColor, + theme.bgHeader + ); + overlayCtx.stroke(); + drawGridHeaders( overlayCtx, effectiveCols, @@ -308,17 +319,10 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { freezeTrailingRows, rows, theme, - true - ); - - overlayCtx.beginPath(); - overlayCtx.moveTo(0, overlayHeight - 0.5); - overlayCtx.lineTo(width, overlayHeight - 0.5); - overlayCtx.strokeStyle = blend( - theme.headerBottomBorderColor ?? theme.horizontalBorderColor ?? theme.borderColor, - theme.bgHeader + true, + arg.columnSelectionGridLines ? selection.columns : undefined, + arg.rowSelectionGridLines ? selection.rows : undefined ); - overlayCtx.stroke(); if (mustDrawHighlightRingsOnHeader) { drawHighlightRings( @@ -716,7 +720,10 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) { verticalBorder, freezeTrailingRows, rows, - theme + theme, + false, + arg.columnSelectionGridLines ? selection.columns : undefined, + arg.rowSelectionGridLines ? selection.rows : undefined ); highlightRedraw?.(); diff --git a/packages/core/src/internal/data-grid/render/draw-grid-arg.ts b/packages/core/src/internal/data-grid/render/draw-grid-arg.ts index 9221f91f7..d1d1bb770 100644 --- a/packages/core/src/internal/data-grid/render/draw-grid-arg.ts +++ b/packages/core/src/internal/data-grid/render/draw-grid-arg.ts @@ -79,4 +79,6 @@ export interface DrawGridArg { readonly getCellRenderer: GetCellRendererCallback; readonly minimumCellWidth: number; readonly resizeIndicator: "full" | "header" | "none"; + readonly columnSelectionGridLines: boolean; + readonly rowSelectionGridLines: boolean; } diff --git a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx index 841eeefbe..7003aa5d5 100644 --- a/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx +++ b/packages/core/src/internal/scrolling-data-grid/scrolling-data-grid.tsx @@ -339,6 +339,8 @@ const GridScroller: React.FunctionComponent = p => { smoothScrollX={p.smoothScrollX} smoothScrollY={p.smoothScrollY} resizeIndicator={p.resizeIndicator} + columnSelectionGridLines={p.columnSelectionGridLines} + rowSelectionGridLines={p.rowSelectionGridLines} /> );