Skip to content

Commit

Permalink
Add optional hover effectto text cell and allow overriding the acitiv…
Browse files Browse the repository at this point in the history
…ation behavior at a per cell level (#890)

* Add optional hover effectto text cell and allow overriding the acitivation behavior at a per cell level

* Fix build
  • Loading branch information
jassmith committed Jan 31, 2024
1 parent 0673b91 commit 92bba1d
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 14 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/cells/cell-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ interface BaseCellRenderer<T extends InnerGridCell> {
readonly kind: T["kind"];
readonly draw: DrawCallback<T>;
readonly drawPrep?: PrepCallback;
readonly needsHover?: boolean;
readonly needsHover?: boolean | ((cell: T) => boolean);
readonly needsHoverPosition?: boolean;
readonly measure?: (ctx: CanvasRenderingContext2D, cell: T, theme: FullTheme) => number;

Expand Down
43 changes: 40 additions & 3 deletions packages/core/src/cells/text-cell.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,55 @@
/* eslint-disable react/display-name */
import * as React from "react";
import { GrowingEntry } from "../internal/growing-entry/growing-entry.js";
import { drawTextCell, prepTextCell } from "../internal/data-grid/render/data-grid-lib.js";
import {
drawTextCell,
measureTextCached,
prepTextCell,
roundedRect,
} from "../internal/data-grid/render/data-grid-lib.js";
import { GridCellKind, type TextCell } from "../internal/data-grid/data-grid-types.js";
import type { InternalCellRenderer } from "./cell-types.js";
import { withAlpha } from "../internal/data-grid/color-parser.js";

export const textCellRenderer: InternalCellRenderer<TextCell> = {
getAccessibilityString: c => c.data?.toString() ?? "",
kind: GridCellKind.Text,
needsHover: false,
needsHover: textCell => textCell.hoverEffect === true,
needsHoverPosition: false,
drawPrep: prepTextCell,
useLabel: true,
draw: a => (drawTextCell(a, a.cell.displayData, a.cell.contentAlign, a.cell.allowWrapping, a.hyperWrapping), true),
draw: a => {
const { cell, hoverAmount, hyperWrapping, ctx, rect, theme, overrideCursor } = a;
const { displayData, contentAlign, hoverEffect, allowWrapping } = cell;
if (hoverEffect === true && hoverAmount > 0) {
ctx.textBaseline = "alphabetic";
const padX = theme.cellHorizontalPadding;
const padY = theme.cellVerticalPadding;
const m = measureTextCached(displayData, ctx, theme.baseFontFull, "alphabetic");
const maxH = rect.height - padY;
const h = Math.min(maxH, m.actualBoundingBoxAscent * 2.5);
ctx.beginPath();
roundedRect(
ctx,
rect.x + padX / 2,
rect.y + (rect.height - h) / 2 + 1,
m.width + padX * 3,
h - 1,
theme.roundingRadius ?? 4
);
ctx.globalAlpha = hoverAmount;
ctx.fillStyle = withAlpha(theme.textDark, 0.1);
ctx.fill();

// restore
ctx.globalAlpha = 1;
ctx.fillStyle = theme.textDark;
ctx.textBaseline = "middle";

overrideCursor?.("text");
}
drawTextCell(a, displayData, contentAlign, allowWrapping, hyperWrapping);
},
measure: (ctx, cell, t) => {
const lines = cell.displayData.split("\n", cell.allowWrapping === true ? undefined : 1);
let maxLineWidth = 0;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/cells/uri-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function getTextRect(
export const uriCellRenderer: InternalCellRenderer<UriCell> = {
getAccessibilityString: c => c.data?.toString() ?? "",
kind: GridCellKind.Uri,
needsHover: true,
needsHover: uriCell => uriCell.hoverEffect === true,
needsHoverPosition: true,
useLabel: true,
drawPrep: prepTextCell,
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/data-editor/data-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
BooleanIndeterminate,
type FillHandleDirection,
type EditListItem,
type CellActiviationBehavior,
} from "../internal/data-grid/data-grid-types.js";
import DataGridSearch, { type DataGridSearchProps } from "../internal/data-grid-search/data-grid-search.js";
import { browserIsOSX } from "../common/browser-detect.js";
Expand Down Expand Up @@ -622,7 +623,7 @@ export interface DataEditorProps extends Props, Pick<DataGridSearchProps, "image
* Determines when a cell is considered activated and will emit the `onCellActivated` event. Generally an activated
* cell will open to edit mode.
*/
readonly cellActivationBehavior?: "double-click" | "single-click" | "second-click";
readonly cellActivationBehavior?: CellActiviationBehavior;

/**
* Controls if focus will trap inside the data grid when doing tab and caret navigation.
Expand Down Expand Up @@ -2223,7 +2224,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
if (isPrevented.current || gridSelection.current === undefined) return false;

let shouldActivate = false;
switch (cellActivationBehavior) {
switch (c.activationBehaviorOverride ?? cellActivationBehavior) {
case "double-click":
case "second-click": {
if (mouse?.previousSelection?.current?.cell === undefined) break;
Expand Down
20 changes: 18 additions & 2 deletions packages/core/src/docs/examples/cell-activated-event.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
defaultProps,
useAllMockedKinds,
} from "../../data-editor/stories/utils.js";
import type { Item } from "../../internal/data-grid/data-grid-types.js";
import type { GridCell, Item } from "../../internal/data-grid/data-grid-types.js";
import { SimpleThemeWrapper } from "../../stories/story-utils.js";
import type { DataEditorCoreProps } from "../../index.js";

Expand All @@ -28,6 +28,21 @@ export default {
export const CellActivatedEvent: React.VFC<Pick<DataEditorCoreProps, "cellActivationBehavior">> = p => {
const { cols, getCellContent, onColumnResize, setCellValue } = useAllMockedKinds();

const getCellContentMangled = React.useCallback(
(item: Item): GridCell => {
const result = getCellContent(item);
if (item[0] === 3) {
return {
...result,
activationBehaviorOverride: "single-click",
hoverEffect: true,
} as any;
}
return result;
},
[getCellContent]
);

const [lastActivated, setLastActivated] = React.useState<Item | undefined>(undefined);

const onCellActivated = React.useCallback((cell: Item) => {
Expand All @@ -51,8 +66,9 @@ export const CellActivatedEvent: React.VFC<Pick<DataEditorCoreProps, "cellActiva
}>
<DataEditor
{...defaultProps}
// editorBloom={[-1, -4]}
cellActivationBehavior={p.cellActivationBehavior}
getCellContent={getCellContent}
getCellContent={getCellContentMangled}
//initialSize={[849, 967]}
//scrollOffsetY={10_000}
getCellsForSelection={true}
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export { markerCellRenderer } from "./cells/marker-cell.js";
export { bubbleCellRenderer } from "./cells/bubble-cell.js";
export { protectedCellRenderer } from "./cells/protected-cell.js";
export { rowIDCellRenderer } from "./cells/row-id-cell.js";
export { AllCellRenderers } from "./cells/index.js";
export { sprites } from "./internal/data-grid/sprites.js";
export { default as ImageWindowLoaderImpl } from "./common/image-window-loader.js";
export * from "./data-editor/copy-paste.js";

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/internal/data-grid/data-grid-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ export function isRectangleEqual(a: Rectangle | undefined, b: Rectangle | undefi
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
}

export type CellActiviationBehavior = "double-click" | "single-click" | "second-click";

/** @category Cells */
export interface BaseGridCell {
readonly allowOverlay: boolean;
Expand All @@ -309,6 +311,7 @@ export interface BaseGridCell {
readonly contentAlign?: "left" | "right" | "center";
readonly cursor?: CSSProperties["cursor"];
readonly copyData?: string;
readonly activationBehaviorOverride?: CellActiviationBehavior;
}

/** @category Cells */
Expand All @@ -331,6 +334,7 @@ export interface TextCell extends BaseGridCell {
readonly data: string;
readonly readonly?: boolean;
readonly allowWrapping?: boolean;
readonly hoverEffect?: boolean;
}

/** @category Cells */
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/internal/data-grid/data-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1198,9 +1198,10 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
}
const cell = getCellContent(hoveredItem as [number, number], true);
const r = getCellRenderer(cell);
am.setHovered(
(r === undefined && cell.kind === GridCellKind.Custom) || r?.needsHover === true ? hoveredItem : undefined
);
const cellNeedsHover =
(r === undefined && cell.kind === GridCellKind.Custom) ||
(r?.needsHover !== undefined && (typeof r.needsHover === "boolean" ? r.needsHover : r.needsHover(cell)));
am.setHovered(cellNeedsHover ? hoveredItem : undefined);
}, [getCellContent, getCellRenderer, hoveredItem]);

const hoveredRef = React.useRef<GridMouseEventArgs>();
Expand Down Expand Up @@ -1236,6 +1237,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
};

if (!mouseEventArgsAreEqual(args, hoveredRef.current)) {
setDrawCursorOverride(undefined);
onItemHovered?.(args);
maybeSetHoveredInfo(
args.kind === outOfBoundsKind ? undefined : [args.location, [args.localEventX, args.localEventY]],
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/internal/data-grid/render/data-grid-lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,13 @@ function makeCacheKey(
}

/** @category Drawing */
export function measureTextCached(s: string, ctx: CanvasRenderingContext2D, font?: string): TextMetrics {
const key = makeCacheKey(s, ctx, "middle", font);
export function measureTextCached(
s: string,
ctx: CanvasRenderingContext2D,
font?: string,
baseline: "middle" | "alphabetic" = "middle"
): TextMetrics {
const key = makeCacheKey(s, ctx, baseline, font);
let metrics = metricsCache[key];
if (metrics === undefined) {
metrics = ctx.measureText(s);
Expand Down
3 changes: 3 additions & 0 deletions packages/core/test/data-grid-lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ function makeCol(title: string, sourceIndex: number, sticky: boolean, width: num
style: undefined,
themeOverride: undefined,
trailingRowOptions: undefined,
growOffset: undefined,
rowMarker: undefined,
rowMarkerChecked: undefined,
};
}

Expand Down

0 comments on commit 92bba1d

Please sign in to comment.