diff --git a/packages/arcs/src/ArcShape.tsx b/packages/arcs/src/ArcShape.tsx index 947014c79..3a23565bd 100644 --- a/packages/arcs/src/ArcShape.tsx +++ b/packages/arcs/src/ArcShape.tsx @@ -1,4 +1,4 @@ -import { useCallback, MouseEvent } from 'react' +import { useCallback, MouseEvent, FocusEvent } from 'react' import { SpringValue, Interpolation, animated } from '@react-spring/web' import { DatumWithArcAndColor } from './types' @@ -7,6 +7,11 @@ export type ArcMouseHandler = ( event: MouseEvent ) => void +export type ArcKeyboardHandler = ( + datum: Datum, + event: FocusEvent +) => void + export interface ArcShapeProps { datum: Datum style: { @@ -17,9 +22,13 @@ export interface ArcShapeProps { path: Interpolation } onClick?: ArcMouseHandler + onMouseDown?: ArcMouseHandler + onMouseUp?: ArcMouseHandler onMouseEnter?: ArcMouseHandler onMouseMove?: ArcMouseHandler onMouseLeave?: ArcMouseHandler + onFocus?: ArcKeyboardHandler + onBlur?: ArcKeyboardHandler } /** @@ -35,6 +44,10 @@ export const ArcShape = ({ onMouseEnter, onMouseMove, onMouseLeave, + onMouseDown, + onMouseUp, + onFocus, + onBlur, }: ArcShapeProps) => { const handleClick = useCallback( (event: MouseEvent) => onClick?.(datum, event), @@ -56,6 +69,23 @@ export const ArcShape = ({ [onMouseLeave, datum] ) + const handleMouseDown = useCallback( + (event: MouseEvent) => onMouseDown?.(datum, event), + [onMouseDown, datum] + ) + const handleMouseUp = useCallback( + (event: MouseEvent) => onMouseUp?.(datum, event), + [onMouseUp, datum] + ) + const handleFocus = useCallback( + (event: FocusEvent) => onFocus?.(datum, event), + [onFocus, datum] + ) + const handleBlur = useCallback( + (event: FocusEvent) => onBlur?.(datum, event), + [onBlur, datum] + ) + return ( ({ stroke={style.borderColor} strokeWidth={style.borderWidth} onClick={onClick ? handleClick : undefined} + onMouseDown={onMouseDown ? handleMouseDown : undefined} + onMouseUp={onMouseUp ? handleMouseUp : undefined} onMouseEnter={onMouseEnter ? handleMouseEnter : undefined} onMouseMove={onMouseMove ? handleMouseMove : undefined} onMouseLeave={onMouseLeave ? handleMouseLeave : undefined} + onFocus={onFocus ? handleFocus : undefined} + onBlur={onBlur ? handleBlur : undefined} + focusable={onFocus || onBlur ? true : undefined} + tabIndex={onFocus || onBlur ? 0 : undefined} /> ) } diff --git a/packages/arcs/src/ArcsLayer.tsx b/packages/arcs/src/ArcsLayer.tsx index 483030d1a..0c49e44d7 100644 --- a/packages/arcs/src/ArcsLayer.tsx +++ b/packages/arcs/src/ArcsLayer.tsx @@ -4,7 +4,7 @@ import { InheritedColorConfig, useInheritedColor } from '@nivo/colors' import { DatumWithArcAndColor, ArcGenerator } from './types' import { useArcsTransition } from './useArcsTransition' import { ArcTransitionMode } from './arcTransitionMode' -import { ArcMouseHandler, ArcShape, ArcShapeProps } from './ArcShape' +import { ArcKeyboardHandler, ArcMouseHandler, ArcShape, ArcShapeProps } from './ArcShape' export type ArcComponent = ( props: ArcShapeProps @@ -17,9 +17,13 @@ interface ArcsLayerProps { borderWidth: number borderColor: InheritedColorConfig onClick?: ArcMouseHandler + onMouseDown?: ArcMouseHandler + onMouseUp?: ArcMouseHandler onMouseEnter?: ArcMouseHandler onMouseMove?: ArcMouseHandler onMouseLeave?: ArcMouseHandler + onFocus?: ArcKeyboardHandler + onBlur?: ArcKeyboardHandler transitionMode: ArcTransitionMode component?: ArcComponent } @@ -34,6 +38,10 @@ export const ArcsLayer = ({ onMouseEnter, onMouseMove, onMouseLeave, + onMouseDown, + onMouseUp, + onFocus, + onBlur, transitionMode, component = ArcShape, }: ArcsLayerProps) => { @@ -88,6 +96,10 @@ export const ArcsLayer = ({ onMouseEnter, onMouseMove, onMouseLeave, + onMouseDown, + onMouseUp, + onFocus, + onBlur, }) })} diff --git a/packages/pie/src/Arcs.tsx b/packages/pie/src/Arcs.tsx index 6e38f5b6d..3d19ac701 100644 --- a/packages/pie/src/Arcs.tsx +++ b/packages/pie/src/Arcs.tsx @@ -11,10 +11,15 @@ interface ArcsProps { borderWidth: CompletePieSvgProps['borderWidth'] borderColor: CompletePieSvgProps['borderColor'] isInteractive: CompletePieSvgProps['isInteractive'] + isFocusable: CompletePieSvgProps['isFocusable'] onClick?: CompletePieSvgProps['onClick'] onMouseEnter?: CompletePieSvgProps['onMouseEnter'] onMouseMove?: CompletePieSvgProps['onMouseMove'] onMouseLeave?: CompletePieSvgProps['onMouseLeave'] + onMouseDown?: CompletePieSvgProps['onMouseDown'] + onMouseUp?: CompletePieSvgProps['onMouseUp'] + onFocus?: CompletePieSvgProps['onFocus'] + onBlur?: CompletePieSvgProps['onBlur'] setActiveId: (id: null | string | number) => void tooltip: CompletePieSvgProps['tooltip'] transitionMode: CompletePieSvgProps['transitionMode'] @@ -27,16 +32,37 @@ export const Arcs = ({ borderWidth, borderColor, isInteractive, + isFocusable, onClick, onMouseEnter, onMouseMove, onMouseLeave, + onMouseDown, + onMouseUp, + onFocus, + onBlur, setActiveId, tooltip, transitionMode, }: ArcsProps) => { const { showTooltipFromEvent, hideTooltip } = useTooltip() + const handleFocus = useMemo(() => { + if (!(isFocusable && isFocusable)) return undefined + + return (datum: ComputedDatum, event: React.FocusEvent) => { + onFocus?.(datum, event) + } + }, [isFocusable, isFocusable , onFocus]) + + const handleBlur = useMemo(() => { + if (!(isInteractive && isFocusable)) return undefined + + return (datum: ComputedDatum, event: React.FocusEvent) => { + onBlur?.(datum, event) + } + }, [isInteractive, isFocusable , onBlur]) + const handleClick = useMemo(() => { if (!isInteractive) return undefined @@ -45,6 +71,22 @@ export const Arcs = ({ } }, [isInteractive, onClick]) + const handleMouseDown = useMemo(() => { + if (!isInteractive) return undefined + + return (datum: ComputedDatum, event: React.MouseEvent) => { + onMouseDown?.(datum, event) + } + }, [isInteractive, onMouseDown]) + + const handleMouseUp = useMemo(() => { + if (!isInteractive) return undefined + + return (datum: ComputedDatum, event: React.MouseEvent) => { + onMouseUp?.(datum, event) + } + }, [isInteractive, onMouseUp]) + const handleMouseEnter = useMemo(() => { if (!isInteractive) return undefined @@ -86,6 +128,10 @@ export const Arcs = ({ onMouseEnter={handleMouseEnter} onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave} + onMouseDown={handleMouseDown} + onMouseUp={handleMouseUp} + onBlur={handleBlur} + onFocus={handleFocus} /> ) } diff --git a/packages/pie/src/Pie.tsx b/packages/pie/src/Pie.tsx index 2f6eb159a..6af16cbf9 100644 --- a/packages/pie/src/Pie.tsx +++ b/packages/pie/src/Pie.tsx @@ -69,10 +69,15 @@ const InnerPie = ({ // interactivity isInteractive = defaultProps.isInteractive, + isFocusable = defaultProps.isFocusable, onClick, onMouseEnter, onMouseMove, onMouseLeave, + onMouseDown, + onMouseUp, + onFocus, + onBlur, tooltip = defaultProps.tooltip, activeId: activeIdFromProps, onActiveIdChange, @@ -147,10 +152,15 @@ const InnerPie = ({ borderWidth={borderWidth} borderColor={borderColor} isInteractive={isInteractive} + isFocusable={isFocusable} onClick={onClick} onMouseEnter={onMouseEnter} onMouseMove={onMouseMove} onMouseLeave={onMouseLeave} + onMouseDown={onMouseDown} + onMouseUp={onMouseUp} + onFocus={onFocus} + onBlur={onBlur} setActiveId={setActiveId} tooltip={tooltip} transitionMode={transitionMode} @@ -241,6 +251,7 @@ const InnerPie = ({ export const Pie = ({ isInteractive = defaultProps.isInteractive, + isFocusable = defaultProps.isFocusable, animate = defaultProps.animate, motionConfig = defaultProps.motionConfig, theme, @@ -251,11 +262,16 @@ export const Pie = ({ {...{ animate, isInteractive, + isFocusable, motionConfig, renderWrapper, theme, }} > - isInteractive={isInteractive} {...otherProps} /> + + isInteractive={isInteractive} + isFocusable={isFocusable} + {...otherProps} + /> ) diff --git a/packages/pie/src/props.ts b/packages/pie/src/props.ts index 7fa42bd87..f5f65b635 100644 --- a/packages/pie/src/props.ts +++ b/packages/pie/src/props.ts @@ -50,7 +50,7 @@ export const defaultProps = { fill: [], isInteractive: true, - + isFocusable: false, animate: true, motionConfig: 'gentle', transitionMode: 'innerRadius' as ArcTransitionMode, diff --git a/packages/pie/src/types.ts b/packages/pie/src/types.ts index 6e0239a21..7fe8c74ee 100644 --- a/packages/pie/src/types.ts +++ b/packages/pie/src/types.ts @@ -70,6 +70,11 @@ export type MouseEventHandler = ( event: React.MouseEvent ) => void +export type FocusEventHandler = ( + datum: ComputedDatum, + event: React.FocusEvent +) => void + export type PieLayerId = 'arcLinkLabels' | 'arcs' | 'arcLabels' | 'legends' export interface PieCustomLayerProps { @@ -112,6 +117,7 @@ export type CommonPieProps = { // interactivity isInteractive: boolean + isFocusable: boolean tooltip: React.FC> activeId: DatumId | null onActiveIdChange: (id: DatumId | null) => void @@ -130,6 +136,10 @@ export type PieHandlers = { onMouseEnter?: MouseEventHandler onMouseMove?: MouseEventHandler onMouseLeave?: MouseEventHandler + onMouseDown?: MouseEventHandler + onMouseUp?: MouseEventHandler + onFocus?: FocusEventHandler + onBlur?: FocusEventHandler } export type PieSvgCustomComponents = {