diff --git a/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx b/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx index fe2bf9866..dacf8796b 100644 --- a/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx +++ b/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx @@ -187,7 +187,6 @@ export function DraggableStructure(props: DraggableStructureProps) { buttons={actionsButtons} fill positioningStrategy="fixed" - position="top-left" direction="row" targetProps={{ style: { width: '100%', height: '100%' } }} space={2} diff --git a/src/component/1d-2d/components/SpectrumInfoBlock.tsx b/src/component/1d-2d/components/SpectrumInfoBlock.tsx index 842a0e015..87db3fca3 100644 --- a/src/component/1d-2d/components/SpectrumInfoBlock.tsx +++ b/src/component/1d-2d/components/SpectrumInfoBlock.tsx @@ -190,7 +190,6 @@ function SpectrumInfoBlock() { targetTagName="g" buttons={actionsButtons} positioningStrategy="fixed" - position="top-left" direction="row" space={2} {...(isMoveActive && { isOpen: true })} diff --git a/src/component/1d/FloatPublicationString.tsx b/src/component/1d/FloatPublicationString.tsx index ae2f5ac40..79f101e6f 100644 --- a/src/component/1d/FloatPublicationString.tsx +++ b/src/component/1d/FloatPublicationString.tsx @@ -289,7 +289,6 @@ function DraggablePublicationString(props: DraggablePublicationStringProps) { buttons={actionButtons} fill positioningStrategy="fixed" - position="top-left" direction="row" targetProps={{ style: { width: '100%', height: '100%' } }} space={2} diff --git a/src/component/1d/FloatingRanges.tsx b/src/component/1d/FloatingRanges.tsx index 78f8ea6e8..658d55373 100644 --- a/src/component/1d/FloatingRanges.tsx +++ b/src/component/1d/FloatingRanges.tsx @@ -321,7 +321,6 @@ function DraggableRanges(props: DraggablePublicationStringProps) { buttons={actionButtons} fill positioningStrategy="fixed" - position="top-left" direction="row" targetProps={{ style: { width: '100%', height: '100%' } }} space={2} diff --git a/src/component/1d/inset/DraggableInset.tsx b/src/component/1d/inset/DraggableInset.tsx index 228405f73..ae1b75136 100644 --- a/src/component/1d/inset/DraggableInset.tsx +++ b/src/component/1d/inset/DraggableInset.tsx @@ -209,7 +209,6 @@ export function DraggableInset(props: Inset) { buttons={actionButtons} fill positioningStrategy="fixed" - position="top-left" direction="row" targetProps={{ style: { width: '100%', height: '100%' } }} space={2} diff --git a/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx b/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx index 845bc1892..30ecfa8d5 100644 --- a/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx +++ b/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx @@ -121,8 +121,8 @@ function PeakAnnotationsSpreadMode(props: PeakAnnotationsSpreadModeProps) { direction="row" {...(isDragActive && { isOpen: true })} y={y} - offsetY={-20} - offsetXMode="cursor" + anchorTo="cursor-x" + anchorPlacement="start" autoFlip={false} > diff --git a/src/component/1d/ranges/Range.tsx b/src/component/1d/ranges/Range.tsx index 038990a1a..cd94cc535 100644 --- a/src/component/1d/ranges/Range.tsx +++ b/src/component/1d/ranges/Range.tsx @@ -156,7 +156,7 @@ function Range(options: RangeProps) { onClosed={() => { isAssignBtnTrigged.current = false; }} - offsetYMode="cursor" + anchorTo="cursor-y" > { isAssignBtnTrigged.current = false; }} diff --git a/src/component/elements/ActionsButtonsPopover.tsx b/src/component/elements/ActionsButtonsPopover.tsx index cb76de1e8..31bbe7c73 100644 --- a/src/component/elements/ActionsButtonsPopover.tsx +++ b/src/component/elements/ActionsButtonsPopover.tsx @@ -51,21 +51,30 @@ function isSeparator( type Direction = 'column' | 'row'; -export interface ActionsButtonsPopoverProps extends Omit< - PopoverProps, - 'interactionKind' | 'content' | 'modifiers' | 'renderTarget' -> { +type AnchorTo = 'element' | 'cursor-x' | 'cursor-y' | 'cursor'; + +type AnchorPlacement = 'start' | 'end'; + +interface Position { + x: number; + y: number; +} +export interface ActionsButtonsPopoverProps + extends + Omit< + PopoverProps, + 'interactionKind' | 'content' | 'modifiers' | 'renderTarget' + >, + Partial { buttons: ActionButtonProps[]; contentStyle?: CSSProperties; direction?: Direction; space?: number; offsetX?: number; offsetY?: number; - offsetYMode?: 'fixed' | 'cursor'; - offsetXMode?: 'fixed' | 'cursor'; - x?: number; - y?: number; + anchorTo?: AnchorTo; autoFlip?: boolean; + anchorPlacement?: AnchorPlacement; } function ActionButton(props: ButtonProps) { @@ -90,6 +99,52 @@ function filterButtons(buttons: ActionButtonProps[]) { return { visibleButtons, disablePopover }; } +interface OffsetOptions { + offsetX?: number; + offsetY?: number; + anchorTo?: AnchorTo; + cursorPosition: Position; +} + +function getOffset(options: OffsetOptions): [number, number] { + const { anchorTo, cursorPosition, offsetX = 0, offsetY = 0 } = options; + const isTrackX = anchorTo === 'cursor-x' || anchorTo === 'cursor'; + const isTrackY = anchorTo === 'cursor-y' || anchorTo === 'cursor'; + + const x = isTrackX ? cursorPosition.x : 0; + const y = isTrackY ? cursorPosition.y : 0; + + if (anchorTo === 'cursor-x') { + return [x + offsetX, -offsetY]; + } + + if (anchorTo === 'cursor-y') { + return [y + offsetY, offsetX]; + } + + if (anchorTo === 'cursor') { + return [x + offsetX, -(y + offsetY)]; + } + + return [offsetX, -offsetY]; +} + +function getPlacement( + anchorTo: AnchorTo, + anchorPlacement: AnchorPlacement, +): PopoverProps['placement'] { + switch (anchorTo) { + case 'cursor-x': + return `${anchorPlacement === 'start' ? 'top' : 'bottom'}-start`; + case 'cursor-y': + return `${anchorPlacement === 'start' ? 'left' : 'right'}-start`; + case 'cursor': + return 'top-start'; + default: + return 'top-start'; + } +} + export function ActionsButtonsPopover(props: ActionsButtonsPopoverProps) { const { targetTagName = 'div', @@ -99,40 +154,42 @@ export function ActionsButtonsPopover(props: ActionsButtonsPopoverProps) { space, direction = 'column', contentStyle = {}, - offsetX: externalOffsetX = 0, - offsetY: externalOffsetY = 0, + offsetX = 0, + offsetY = 0, x, y, - offsetYMode = 'fixed', - offsetXMode = 'fixed', - autoFlip = true, + anchorTo = 'element', + autoFlip = false, disabled, + anchorPlacement = 'end', ...otherProps } = props; - const [cursor, setCursor] = useState({ x: 0, y: 0 }); + const [cursorPosition, setCursorPosition] = useState({ + x: 0, + y: 0, + }); const Wrapper = targetTagName as any; const { visibleButtons, disablePopover } = filterButtons(buttons); - const offsetY = offsetYMode === 'fixed' ? externalOffsetY : cursor.y; - const offsetX = offsetXMode === 'fixed' ? externalOffsetX : cursor.x; - function handleMouseEnter(event: any) { const { clientX, clientY, currentTarget } = event; if (!(currentTarget instanceof Element)) return; const rect = currentTarget.getBoundingClientRect(); - setCursor((prev) => ({ - x: offsetXMode === 'cursor' ? clientX - rect.left : prev.x, - y: offsetYMode === 'cursor' ? clientY - rect.top : prev.y, - })); + setCursorPosition({ + x: clientX - rect.left, + y: clientY - rect.top, + }); } + const placement = getPlacement(anchorTo, anchorPlacement); + const offset = getOffset({ anchorTo, offsetX, offsetY, cursorPosition }); return (