From f92e8319325d56956731d9b5669c10004af1290b Mon Sep 17 00:00:00 2001 From: Pierre Poupin Date: Sun, 6 Nov 2022 21:06:31 +0100 Subject: [PATCH] feature: add mouse events for arrows Allows for clickable arrows, drag and droppable arrows... --- README.md | 3 + example/App.tsx | 6 +- example/TenthExample.tsx | 113 +++++++++++++++++++ src/ArcherContainer/components/SvgArrows.tsx | 12 ++ src/ArcherElement/ArcherElement.helpers.ts | 6 + src/SvgArrow/SvgArrow.tsx | 25 +++- src/types.ts | 11 ++ 7 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 example/TenthExample.tsx diff --git a/README.md b/README.md index 7d29a67..e5035c4 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,9 @@ The `Relation` type has the following shape: order?: number, // higher order means arrow will be drawn on top of the others className?: string, // CSS class selectors on the SVG arrow style: ArcherStyle, + domAttributes?: DOMAttributes, // Allows to make selectable arrows by passing dom attributes like onMouseHover + cursor?: Property.Cursor, // Allows to customize the hovering cursor of the arrow. Will only work if domAttributes is present + hitSlop?: number, // Allows to make the selectable arrow thicker. Will only work if domAttributes is present } ``` diff --git a/example/App.tsx b/example/App.tsx index cf5ad83..8e69807 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -8,6 +8,7 @@ import SixthExample from './SixthExample'; import SeventhExample from './SeventhExample'; import EighthExample from './EighthExample'; import NinthExample from './NinthExample'; +import TenthExample from './TenthExample'; const getExample = (id: number) => { switch (id) { @@ -38,6 +39,9 @@ const getExample = (id: number) => { case 9: return NinthExample; + case 10: + return TenthExample; + default: return SecondExample; } @@ -54,7 +58,7 @@ const App = () => {

Example {exampleId}

Choose an example:

- {[...Array(9).keys()].map((value) => ( + {[...Array(10).keys()].map((value) => (
+ ); +}; + +export default TenthExample; diff --git a/src/ArcherContainer/components/SvgArrows.tsx b/src/ArcherContainer/components/SvgArrows.tsx index c6ed4a4..9944ed8 100644 --- a/src/ArcherContainer/components/SvgArrows.tsx +++ b/src/ArcherContainer/components/SvgArrows.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Property } from 'csstype'; import Vector2 from '../../geometry/Vector2'; import { getPointCoordinatesFromAnchorPosition, @@ -21,6 +22,8 @@ interface CommonProps { offset: ArcherContainerProps['offset']; uniqueId: string; refs: Record; + hitSlop?: number; + cursor?: Property.Cursor; } const AdaptedArrow = ( @@ -35,6 +38,9 @@ const AdaptedArrow = ( const newEndShape = createShapeObj(style, props.endShape); + const domAttributes = props.domAttributes; + const cursor = props.cursor; + const hitSlop = props.hitSlop; const newStrokeColor = style.strokeColor || props.strokeColor; const newStrokeWidth = style.strokeWidth || props.strokeWidth; const newStrokeDasharray = style.strokeDasharray || props.strokeDasharray; @@ -86,6 +92,9 @@ const AdaptedArrow = ( enableStartMarker={!!newStartMarker} disableEndMarker={!newEndMarker} endShape={newEndShape} + domAttributes={domAttributes} + hitSlop={hitSlop} + cursor={cursor} /> ); }; @@ -116,6 +125,9 @@ export const SvgArrows = ( className={currentRelation.className} label={currentRelation.label} style={currentRelation.style || {}} + domAttributes={currentRelation.domAttributes} + hitSlop={currentRelation.hitSlop} + cursor={currentRelation.cursor} startMarker={props.startMarker} endMarker={props.endMarker} endShape={props.endShape} diff --git a/src/ArcherElement/ArcherElement.helpers.ts b/src/ArcherElement/ArcherElement.helpers.ts index a792933..0d25dd4 100644 --- a/src/ArcherElement/ArcherElement.helpers.ts +++ b/src/ArcherElement/ArcherElement.helpers.ts @@ -25,6 +25,9 @@ export const generateSourceToTarget = ( className, style, order = 0, + domAttributes, + cursor, + hitSlop, }: RelationType) => ({ source: { id: encodeId(id), @@ -38,6 +41,9 @@ export const generateSourceToTarget = ( label, style, order, + domAttributes, + cursor, + hitSlop, }), ); }; diff --git a/src/SvgArrow/SvgArrow.tsx b/src/SvgArrow/SvgArrow.tsx index 7800257..91c5c84 100644 --- a/src/SvgArrow/SvgArrow.tsx +++ b/src/SvgArrow/SvgArrow.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { DOMAttributes } from 'react'; +import { Property } from 'csstype'; import Vector2 from '../geometry/Vector2'; import { AnchorPositionType, ValidLineStyles } from '../types'; import { computeArrowDirectionVector } from './SvgArrow.helper'; @@ -19,6 +20,9 @@ type Props = { enableStartMarker?: boolean; disableEndMarker?: boolean; endShape: Record; + domAttributes?: DOMAttributes; + hitSlop?: number; + cursor?: Property.Cursor; }; export function computeArrowPointAccordingToArrowHead( @@ -198,6 +202,9 @@ const SvgArrow = ({ enableStartMarker, disableEndMarker, endShape, + domAttributes, + hitSlop = 10, + cursor = 'pointer', }: Props) => { const actualArrowLength = endShape.circle ? endShape.circle.radius * 2 @@ -280,6 +287,22 @@ const SvgArrow = ({ markerStart={enableStartMarker ? markerUrl : undefined} markerEnd={disableEndMarker ? undefined : markerUrl} /> + + {/* This a thicker fake path to grab DOM events - makes clicking on the arrow more usable */} + {domAttributes && ( + + )} + {arrowLabel && ( ; + /** Allows to make the mouse selectable arrow thicker */ + hitSlop?: number; + /** Allows to customize the hovering cursor for a selectable arrow */ + cursor?: Property.Cursor; }; export type EntityRelationType = { @@ -24,6 +32,9 @@ export type SourceToTargetType = { order: number; label?: React.ReactNode | null | undefined; style?: LineType; + domAttributes?: DOMAttributes; + hitSlop?: number; + cursor?: Property.Cursor; }; export type ArrowShapeType = {