mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
[skip ci] feat: prepare for public release
This commit is contained in:
199 files changed
+38391
-33358
No files matched your search
@@ -65,10 +65,9 @@ export function useAntiShiftFloating(
|
||||
} = options;
|
||||
|
||||
const floatingRef = React.useRef<HTMLElement>(null);
|
||||
const [state, setState] = React.useState<FloatingState>({
|
||||
x: -9999,
|
||||
y: -9999,
|
||||
isReady: false,
|
||||
const [state, setState] = React.useState<FloatingState>(() => {
|
||||
const {x, y} = target ? getInitialGuess(target, placement, offsetMainAxis, offsetCrossAxis) : {x: -9999, y: -9999};
|
||||
return {x, y, isReady: false};
|
||||
});
|
||||
const cleanupRef = React.useRef<(() => void) | null>(null);
|
||||
const isCalculatingRef = React.useRef(false);
|
||||
@@ -93,13 +92,6 @@ export function useAntiShiftFloating(
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(floating.style, {
|
||||
visibility: 'hidden',
|
||||
position: 'fixed',
|
||||
left: '-9999px',
|
||||
top: '-9999px',
|
||||
});
|
||||
|
||||
const adaptivePadding = enableSmartBoundary ? getAdaptivePadding() : 8;
|
||||
const shiftPadding = Math.max(6, adaptivePadding);
|
||||
|
||||
@@ -207,3 +199,50 @@ export function useAntiShiftFloating(
|
||||
updatePosition,
|
||||
};
|
||||
}
|
||||
|
||||
function getInitialGuess(target: HTMLElement, placement: Placement, offsetMainAxis: number, offsetCrossAxis: number) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
const [side, align = 'center'] = placement.split('-') as [string, string];
|
||||
|
||||
let x = rect.left;
|
||||
let y = rect.top;
|
||||
|
||||
switch (side) {
|
||||
case 'right':
|
||||
x = rect.right + offsetMainAxis;
|
||||
break;
|
||||
case 'left':
|
||||
x = rect.left - offsetMainAxis;
|
||||
break;
|
||||
case 'bottom':
|
||||
y = rect.bottom + offsetMainAxis;
|
||||
break;
|
||||
case 'top':
|
||||
y = rect.top - offsetMainAxis;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (side === 'top' || side === 'bottom') {
|
||||
if (align === 'end') {
|
||||
x = rect.right;
|
||||
} else if (align === 'start') {
|
||||
x = rect.left;
|
||||
} else {
|
||||
x = rect.left + rect.width / 2;
|
||||
}
|
||||
x += offsetCrossAxis;
|
||||
} else {
|
||||
if (align === 'end') {
|
||||
y = rect.bottom;
|
||||
} else if (align === 'start') {
|
||||
y = rect.top;
|
||||
} else {
|
||||
y = rect.top + rect.height / 2;
|
||||
}
|
||||
y += offsetCrossAxis;
|
||||
}
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
@@ -34,6 +34,7 @@ export function useReactionTooltip(hoverDelay = 500) {
|
||||
const hoverTimerRef = React.useRef<NodeJS.Timeout | null>(null);
|
||||
const closeTimerRef = React.useRef<NodeJS.Timeout | null>(null);
|
||||
const isCalculatingRef = React.useRef(false);
|
||||
const lastPointerRef = React.useRef<{x: number; y: number} | null>(null);
|
||||
|
||||
const [state, setState] = React.useState<ReactionTooltipState>({
|
||||
x: 0,
|
||||
@@ -58,6 +59,17 @@ export function useReactionTooltip(hoverDelay = 500) {
|
||||
return !!targetRef.current?.contains(node) || !!tooltipRef.current?.contains(node);
|
||||
}, []);
|
||||
|
||||
const updateLastPointer = React.useCallback((event: MouseEvent | React.MouseEvent | PointerEvent) => {
|
||||
lastPointerRef.current = {x: event.clientX, y: event.clientY};
|
||||
}, []);
|
||||
|
||||
const shouldKeepOpen = React.useCallback(() => {
|
||||
const lastPointer = lastPointerRef.current;
|
||||
if (!lastPointer) return false;
|
||||
const element = document.elementFromPoint(lastPointer.x, lastPointer.y);
|
||||
return isInHoverRegion(element);
|
||||
}, [isInHoverRegion]);
|
||||
|
||||
const updatePosition = React.useCallback(async () => {
|
||||
if (!state.isOpen || !targetRef.current || !tooltipRef.current || isCalculatingRef.current) {
|
||||
return;
|
||||
@@ -110,6 +122,15 @@ export function useReactionTooltip(hoverDelay = 500) {
|
||||
setState({x: 0, y: 0, isOpen: false, isReady: false});
|
||||
}, [clearTimers]);
|
||||
|
||||
const scheduleHide = React.useCallback(() => {
|
||||
closeTimerRef.current = setTimeout(() => {
|
||||
if (shouldKeepOpen()) {
|
||||
return;
|
||||
}
|
||||
hide();
|
||||
}, 100);
|
||||
}, [hide, shouldKeepOpen]);
|
||||
|
||||
const handleMouseEnter = React.useCallback(() => {
|
||||
clearTimers();
|
||||
if (state.isOpen) return;
|
||||
@@ -121,35 +142,37 @@ export function useReactionTooltip(hoverDelay = 500) {
|
||||
const handleMouseLeave = React.useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
clearTimers();
|
||||
updateLastPointer(event);
|
||||
|
||||
if (isInHoverRegion(event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeTimerRef.current = setTimeout(() => {
|
||||
hide();
|
||||
}, 100);
|
||||
scheduleHide();
|
||||
},
|
||||
[hide, clearTimers],
|
||||
[clearTimers, isInHoverRegion, scheduleHide, updateLastPointer],
|
||||
);
|
||||
|
||||
const handleTooltipMouseEnter = React.useCallback(() => {
|
||||
clearTimers();
|
||||
}, [clearTimers]);
|
||||
const handleTooltipMouseEnter = React.useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
clearTimers();
|
||||
updateLastPointer(event);
|
||||
},
|
||||
[clearTimers, updateLastPointer],
|
||||
);
|
||||
|
||||
const handleTooltipMouseLeave = React.useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
clearTimers();
|
||||
updateLastPointer(event);
|
||||
|
||||
if (isInHoverRegion(event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeTimerRef.current = setTimeout(() => {
|
||||
hide();
|
||||
}, 100);
|
||||
scheduleHide();
|
||||
},
|
||||
[hide, clearTimers, isInHoverRegion],
|
||||
[clearTimers, isInHoverRegion, scheduleHide, updateLastPointer],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -183,6 +206,24 @@ export function useReactionTooltip(hoverDelay = 500) {
|
||||
};
|
||||
}, [state.isOpen, updatePosition]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!state.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlePointerMove = (event: PointerEvent | MouseEvent) => {
|
||||
lastPointerRef.current = {x: event.clientX, y: event.clientY};
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMove, {passive: true});
|
||||
window.addEventListener('mousemove', handlePointerMove, {passive: true});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('mousemove', handlePointerMove);
|
||||
};
|
||||
}, [state.isOpen]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
clearTimers();
|
||||
|
||||
Reference in new issue
Block a user