Position Utilities
Position engine: calculate tooltip placement with collision detection, viewport clamping, and RTL layout support
Calculate tooltip and card positions relative to target elements. Includes viewport collision detection and RTL layout support.
Why Use These Utilities?
Tour cards need to be positioned correctly relative to their targets:
- Multiple placements - Top, bottom, left, right with alignments
- Collision detection - Flip when hitting viewport edges
- RTL support - Mirror placements for right-to-left layouts
- Responsive - Adjust based on available space
calculatePosition
Calculate the position for a tooltip relative to a target element.
Usage
import { calculatePosition, getElementRect } from '@tour-kit/core';
const target = document.getElementById('button');
const targetRect = getElementRect(target);
const tooltipSize = { width: 300, height: 150 };
const position = calculatePosition(
targetRect,
tooltipSize,
'bottom', // placement
[0, 8] // offset [x, y]
);
// position = { x: 150, y: 248 }Parameters
Prop
Type
Return Value
{ x: number; y: number }calculatePositionWithCollision
Calculate position with automatic fallback when hitting viewport edges.
Usage
import { calculatePositionWithCollision, getElementRect } from '@tour-kit/core';
const targetRect = getElementRect(document.getElementById('button'));
const tooltipSize = { width: 300, height: 150 };
const result = calculatePositionWithCollision(
targetRect,
tooltipSize,
'top',
{
offset: [0, 8],
padding: 16,
}
);
// result = { x: 150, y: 50, placement: 'bottom', hasOverflow: false }
// Note: 'top' was requested but 'bottom' was used due to collisionParameters
Prop
Type
Options
Prop
Type
Return Value
interface PositionResult {
x: number;
y: number;
placement: Placement; // Actual placement used
hasOverflow: boolean; // True if no placement fit
}Placement Options
// Simple side placements
'top' // Centered above target
'bottom' // Centered below target
'left' // Centered to the left
'right' // Centered to the right// With alignment
'top-start' // Above, aligned to start (left in LTR)
'top-end' // Above, aligned to end (right in LTR)
'bottom-start' // Below, aligned to start
'bottom-end' // Below, aligned to end
'left-start' // Left, aligned to top
'left-end' // Left, aligned to bottom
'right-start' // Right, aligned to top
'right-end' // Right, aligned to bottomRTL Support
getDocumentDirection
Detect the document's text direction.
import { getDocumentDirection } from '@tour-kit/core';
const dir = getDocumentDirection(); // 'ltr' or 'rtl'mirrorPlacementForRTL
Mirror a placement for RTL layouts.
import { mirrorPlacementForRTL } from '@tour-kit/core';
// In RTL mode:
mirrorPlacementForRTL('left', true); // 'right'
mirrorPlacementForRTL('right-start', true); // 'left-end'
mirrorPlacementForRTL('top-start', true); // 'top-end'
mirrorPlacementForRTL('bottom', true); // 'bottom' (unchanged)RTL-Aware Positioning
import {
calculatePositionWithCollision,
getDocumentDirection,
mirrorPlacementForRTL,
} from '@tour-kit/core';
function getTooltipPosition(target, size, placement) {
const isRTL = getDocumentDirection() === 'rtl';
const effectivePlacement = mirrorPlacementForRTL(placement, isRTL);
return calculatePositionWithCollision(
getElementRect(target),
size,
effectivePlacement
);
}Helper Functions
getElementRect
Get element position including scroll offset.
import { getElementRect } from '@tour-kit/core';
const rect = getElementRect(element);
// { x: 100, y: 200, width: 150, height: 40 }Unlike getBoundingClientRect(), this includes scroll offset so positions work correctly after scrolling.
getViewportDimensions
Get current viewport size.
import { getViewportDimensions } from '@tour-kit/core';
const viewport = getViewportDimensions();
// { width: 1920, height: 1080 }parsePlacement
Parse placement string into side and alignment.
import { parsePlacement } from '@tour-kit/core';
parsePlacement('top-start');
// { side: 'top', alignment: 'start' }
parsePlacement('bottom');
// { side: 'bottom', alignment: 'center' }getOppositeSide
Get the opposite side for fallback positioning.
import { getOppositeSide } from '@tour-kit/core';
getOppositeSide('top'); // 'bottom'
getOppositeSide('left'); // 'right'getFallbackPlacements
Get fallback placements for collision handling.
import { getFallbackPlacements } from '@tour-kit/core';
getFallbackPlacements('top');
// ['bottom', 'left', 'right']
getFallbackPlacements('top-start');
// ['bottom-start', 'left-start', 'right-start', 'top-end']wouldOverflow
Check if a position would cause viewport overflow.
import { wouldOverflow, getViewportDimensions } from '@tour-kit/core';
const overflow = wouldOverflow(
{ x: -10, y: 50 },
{ width: 200, height: 100 },
getViewportDimensions()
);
// { top: false, right: false, bottom: false, left: true }Complete Positioning Example
import {
calculatePositionWithCollision,
getElementRect,
getDocumentDirection,
mirrorPlacementForRTL,
} from '@tour-kit/core';
function TourTooltip({ target, placement = 'bottom' }) {
const tooltipRef = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [actualPlacement, setActualPlacement] = useState(placement);
useEffect(() => {
if (!tooltipRef.current) return;
const targetRect = getElementRect(target);
const tooltipSize = {
width: tooltipRef.current.offsetWidth,
height: tooltipRef.current.offsetHeight,
};
// Handle RTL
const isRTL = getDocumentDirection() === 'rtl';
const effectivePlacement = mirrorPlacementForRTL(placement, isRTL);
// Calculate with collision detection
const result = calculatePositionWithCollision(
targetRect,
tooltipSize,
effectivePlacement,
{ offset: [0, 8], padding: 16 }
);
setPosition({ x: result.x, y: result.y });
setActualPlacement(result.placement);
}, [target, placement]);
return (
<div
ref={tooltipRef}
style={{
position: 'absolute',
left: position.x,
top: position.y,
}}
data-placement={actualPlacement}
>
Tooltip content
</div>
);
}Types
type Side = 'top' | 'bottom' | 'left' | 'right';
type Alignment = 'start' | 'center' | 'end';
type Placement =
| Side
| `${Side}-start`
| `${Side}-end`;
interface Rect {
x: number;
y: number;
width: number;
height: number;
}
interface Position {
x: number;
y: number;
}Related
- TourCard - Uses position utilities
- useElementPosition - React hook for element tracking
- DOM Utilities - Element resolution