refactor(tooltip): add use-reletive and use-adjust-placement to refactor code
This commit is contained in:
parent
fbaeb43a8a
commit
927963c4a3
|
@ -28,3 +28,26 @@ export interface UseCalculatePosition {
|
|||
arrowBound: DOMRect
|
||||
) => TooltipPosition;
|
||||
}
|
||||
|
||||
export interface UseRelative {
|
||||
getRelativeElementBound: () => DOMRect;
|
||||
}
|
||||
|
||||
export interface UseAdjustPlacement {
|
||||
adjustPlacement: (
|
||||
placementAndAlignment: TooltipPlacement,
|
||||
referenceBoundingRect: DOMRect,
|
||||
arrowReferenceBoundingRect: DOMRect,
|
||||
tooltipBound: DOMRect,
|
||||
arrowBound: DOMRect
|
||||
) => TooltipPlacement;
|
||||
}
|
||||
|
||||
export interface UseAdjustPosition {
|
||||
adjustPosition: (
|
||||
placementAndAlignment: TooltipPlacement,
|
||||
originalPosition: RectPosition,
|
||||
relativeElementRect: DOMRect,
|
||||
tooltipRect: DOMRect
|
||||
) => RectPosition;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { SetupContext } from "vue";
|
||||
import { TooltipPlacement, TooltipProps } from "../tooltip.props";
|
||||
import { RectDirection, RectSizeProperty } from "./types";
|
||||
|
||||
export function useAdjustPlacement(props: TooltipProps, context: SetupContext) {
|
||||
|
||||
const revertDirectionMap = new Map<RectDirection, RectDirection>(
|
||||
[['top', 'bottom'], ['bottom', 'top'], ['left', 'right'], ['right', 'left']]
|
||||
);
|
||||
|
||||
const directionBoundMap = new Map<RectDirection, RectSizeProperty>(
|
||||
[['top', 'height'], ['bottom', 'height'], ['left', 'width'], ['right', 'width']]
|
||||
);
|
||||
|
||||
function revert(placement: string, direction: RectDirection): TooltipPlacement {
|
||||
const revertDirection: RectDirection = revertDirectionMap.get(direction) || direction;
|
||||
const revertedPlacement = placement.replace(direction, revertDirection) as TooltipPlacement;
|
||||
return revertedPlacement;
|
||||
}
|
||||
|
||||
function adjustPlacement(
|
||||
placementAndAlignment: TooltipPlacement,
|
||||
referenceBoundingRect: DOMRect,
|
||||
arrowReferenceBoundingRect: DOMRect,
|
||||
tooltipBound: DOMRect,
|
||||
arrowBound: DOMRect): TooltipPlacement {
|
||||
let adjustedResult = placementAndAlignment;
|
||||
const placement = placementAndAlignment.split('-')[0] as RectDirection;
|
||||
const boundProperty = directionBoundMap.get(placement) as RectSizeProperty;
|
||||
const boundSize = tooltipBound[boundProperty] + arrowBound[boundProperty];
|
||||
const referenceBoundSize = Math.abs(arrowReferenceBoundingRect[placement] - referenceBoundingRect[placement]);
|
||||
|
||||
if (referenceBoundSize < boundSize) {
|
||||
adjustedResult = revert(placementAndAlignment, placement);
|
||||
}
|
||||
return adjustedResult;
|
||||
}
|
||||
|
||||
return { adjustPlacement };
|
||||
}
|
|
@ -1,13 +1,55 @@
|
|||
import { SetupContext } from "vue";
|
||||
import { TooltipProps } from "../tooltip.props";
|
||||
import { TooltipPlacement, TooltipProps } from "../tooltip.props";
|
||||
import { RectPosition, TooltipPosition } from "./types";
|
||||
|
||||
export function useTooltipPosition(
|
||||
props: TooltipProps,
|
||||
context: SetupContext,
|
||||
hostBound: DOMRect,
|
||||
tooltipContentBound: DOMRect,
|
||||
tooltipBound: DOMRect,
|
||||
arrowBound: DOMRect) {
|
||||
export function useAdjustPosition(props: TooltipProps, context: SetupContext) {
|
||||
|
||||
const rectifyGutter = 6;
|
||||
|
||||
/**
|
||||
* 判断是否超出边界
|
||||
*/
|
||||
function isOverstepBoundary(referenceBoundingRect: DOMRect, direct: string, value: number) {
|
||||
let overBound = false;
|
||||
let fixedValue = value;
|
||||
|
||||
if ((direct === 'left' || direct === 'top') && value <= referenceBoundingRect[direct]) {
|
||||
overBound = true;
|
||||
fixedValue = referenceBoundingRect[direct] + rectifyGutter;
|
||||
}
|
||||
if ((direct === 'right' || direct === 'bottom') && value >= referenceBoundingRect[direct]) {
|
||||
overBound = true;
|
||||
fixedValue = referenceBoundingRect[direct] - rectifyGutter;
|
||||
}
|
||||
return { overBound, fixedValue };
|
||||
}
|
||||
|
||||
function adjustPosition(
|
||||
placementAndAlignment: TooltipPlacement,
|
||||
originalPosition: TooltipPosition,
|
||||
relativeElementRect: DOMRect,
|
||||
tooltipRect: DOMRect
|
||||
): TooltipPosition {
|
||||
let fixedLeft = originalPosition.tooltip.left;
|
||||
let fixedTop = originalPosition.tooltip.top;
|
||||
const placementAndAlignmentArray = placementAndAlignment.split('-');
|
||||
const placement = placementAndAlignmentArray[0];
|
||||
if (['top', 'bottom'].includes(placement)) {
|
||||
const overLeftBound = isOverstepBoundary(relativeElementRect, 'left', originalPosition.tooltip.left);
|
||||
const overRightBound = isOverstepBoundary(relativeElementRect, 'right', originalPosition.tooltip.left + tooltipRect.width);
|
||||
fixedLeft = overLeftBound.overBound ?
|
||||
overLeftBound.fixedValue :
|
||||
(overRightBound.overBound ? overRightBound.fixedValue - tooltipRect.width : originalPosition.tooltip.left);
|
||||
}
|
||||
const overTopBound = isOverstepBoundary(relativeElementRect, 'top', originalPosition.tooltip.top);
|
||||
const overBottomBound = isOverstepBoundary(relativeElementRect, 'bottom', originalPosition.tooltip.top + tooltipRect.height);
|
||||
fixedTop = overTopBound.overBound ?
|
||||
overTopBound.fixedValue :
|
||||
(overBottomBound.overBound ? overBottomBound.fixedValue - tooltipRect.height : originalPosition.tooltip.top);
|
||||
const tooltip = { left: fixedLeft, top: fixedTop };
|
||||
const arrow = { left: originalPosition.arrow.left, top: originalPosition.arrow.top };
|
||||
return { arrow, tooltip };
|
||||
}
|
||||
|
||||
return { adjustPosition };
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { SetupContext } from "vue";
|
||||
import { TooltipPlacement, TooltipProps } from "../tooltip.props";
|
||||
import { RectPosition } from "./types";
|
||||
import { RectPosition, TooltipPosition } from "./types";
|
||||
|
||||
export function useCalculatePosition(
|
||||
props: TooltipProps,
|
||||
context: SetupContext) {
|
||||
export function useCalculatePosition(props: TooltipProps, context: SetupContext) {
|
||||
|
||||
const space = 6;
|
||||
|
||||
function calculateArrowPosition(
|
||||
placementAndAlignment: TooltipPlacement,
|
||||
|
@ -83,7 +83,7 @@ export function useCalculatePosition(
|
|||
tooltipBound: DOMRect,
|
||||
tooltipContentBound: DOMRect,
|
||||
arrowBound: DOMRect
|
||||
) {
|
||||
): TooltipPosition {
|
||||
const tooltip = calculateTooltipPosition(placementAndAlignment, hostBound, tooltipBound, tooltipContentBound, arrowBound);
|
||||
const arrow = calculateArrowPosition(placementAndAlignment, hostBound, tooltipBound, tooltipContentBound, arrowBound);
|
||||
return { arrow, tooltip };
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { ref, SetupContext } from "vue";
|
||||
import { TooltipProps } from "../tooltip.props";
|
||||
import { UseRelative } from "./types";
|
||||
|
||||
export function useRelative(props: TooltipProps, context: SetupContext): UseRelative {
|
||||
|
||||
const horizontalRelativeElement = ref(props.horizontalRelative);
|
||||
|
||||
const verticalRelativeElement = ref(props.verticalRelative);
|
||||
|
||||
/**
|
||||
* 获取纠正元素
|
||||
*/
|
||||
function getRelativeElement(relativeElement: any) {
|
||||
if (relativeElement instanceof HTMLElement) {
|
||||
return relativeElement;
|
||||
}
|
||||
if (typeof relativeElement == 'string') {
|
||||
return document.querySelector(relativeElement as string);
|
||||
}
|
||||
return relativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认参照的边界
|
||||
*/
|
||||
function getRelativeElementBound(): DOMRect {
|
||||
let right = document.documentElement.clientWidth;
|
||||
let bottom = document.documentElement.clientHeight;
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let height = bottom - top;
|
||||
let width = right - left;
|
||||
// 横向参照
|
||||
if (horizontalRelativeElement.value) {
|
||||
const rectifyReferenceHEl = getRelativeElement(horizontalRelativeElement.value);
|
||||
({ left, right, x, width } = rectifyReferenceHEl.getBoundingClientRect());
|
||||
}
|
||||
// 纵向参照
|
||||
if (verticalRelativeElement.value) {
|
||||
const rectifyReferenceVEl = getRelativeElement(verticalRelativeElement.value);
|
||||
({ bottom, top, y, height } = rectifyReferenceVEl.getBoundingClientRect());
|
||||
}
|
||||
return { top, left, right, bottom, height, width, x, y } as DOMRect;
|
||||
}
|
||||
|
||||
return { getRelativeElementBound };
|
||||
}
|
|
@ -1,173 +1,53 @@
|
|||
import { computed, ref, SetupContext } from "vue";
|
||||
import { TooltipPlacement, TooltipProps } from "../tooltip.props";
|
||||
import { RectDirection, RectPosition, RectSizeProperty } from "./types";
|
||||
import { TooltipProps } from "../tooltip.props";
|
||||
import { RectPosition, TooltipPosition } from "./types";
|
||||
import { useAdjustPlacement } from "./use-adjust-placement";
|
||||
import { useAdjustPosition } from "./use-adjust-position";
|
||||
import { useCalculatePosition } from "./use-calculate-position";
|
||||
import { useRelative } from "./use-relative";
|
||||
|
||||
export function useTooltipPosition(
|
||||
props: TooltipProps,
|
||||
context: SetupContext,
|
||||
hostBound: DOMRect,
|
||||
tooltipContentBound: DOMRect,
|
||||
tooltipBound: DOMRect,
|
||||
arrowBound: DOMRect) {
|
||||
|
||||
const space = 6;
|
||||
|
||||
const rectifyGutter = 6;
|
||||
hostRect: DOMRect,
|
||||
tooltipRect: DOMRect,
|
||||
tooltipContentRect: DOMRect,
|
||||
arrowRect: DOMRect) {
|
||||
|
||||
const { scrollLeft, scrollTop } = document.documentElement;
|
||||
|
||||
const revertDirectionMap = new Map<RectDirection, RectDirection>(
|
||||
[['top', 'bottom'], ['bottom', 'top'], ['left', 'right'], ['right', 'left']]
|
||||
);
|
||||
|
||||
const directionBoundMap = new Map<RectDirection, RectSizeProperty>(
|
||||
[['top', 'height'], ['bottom', 'height'], ['left', 'width'], ['right', 'width']]
|
||||
);
|
||||
|
||||
const placementAndAlignment = ref(props.placement);
|
||||
|
||||
const rectifyReferenceH = ref(props.referenceH);
|
||||
const { getRelativeElementBound } = useRelative(props, context);
|
||||
|
||||
const rectifyReferenceV = ref(props.referenceV);
|
||||
|
||||
function revertPlacement(placement: string, direction: RectDirection): TooltipPlacement {
|
||||
const revertDirection: RectDirection = revertDirectionMap.get(direction) || direction;
|
||||
const revertedPlacement = placement.replace(direction, revertDirection) as TooltipPlacement;
|
||||
return revertedPlacement;
|
||||
}
|
||||
|
||||
function autoRectifyDirection(
|
||||
placement: TooltipPlacement,
|
||||
referenceBoundingRect: DOMRect,
|
||||
arrowReferenceBoundingRect: DOMRect,
|
||||
tooltipBound: DOMRect,
|
||||
arrowBound: DOMRect) {
|
||||
let rectifyPlacement = placement;
|
||||
const direction = placement.split('-')[0] as RectDirection;
|
||||
const boundProperty = directionBoundMap.get(direction) as RectSizeProperty;
|
||||
const boundSize = tooltipBound[boundProperty] + arrowBound[boundProperty];
|
||||
const referenceBoundSize = Math.abs(arrowReferenceBoundingRect[direction] - referenceBoundingRect[direction]);
|
||||
|
||||
if (referenceBoundSize < boundSize) {
|
||||
rectifyPlacement = revertPlacement(rectifyPlacement, direction);
|
||||
}
|
||||
return rectifyPlacement;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取纠正元素
|
||||
*/
|
||||
function getRectifyReferenceElement(referenceEl: any) {
|
||||
if (referenceEl instanceof HTMLElement) {
|
||||
return referenceEl;
|
||||
}
|
||||
if (typeof referenceEl == 'string') {
|
||||
return document.querySelector(referenceEl as string);
|
||||
}
|
||||
return referenceEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认参照的边界
|
||||
*/
|
||||
function getReferenceBound(): DOMRect {
|
||||
let right = document.documentElement.clientWidth;
|
||||
let bottom = document.documentElement.clientHeight;
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let height = bottom - top;
|
||||
let width = right - left;
|
||||
// 横向参照
|
||||
if (rectifyReferenceH.value) {
|
||||
const rectifyReferenceHEl = getRectifyReferenceElement(rectifyReferenceH.value);
|
||||
({ left, right, x, width } = rectifyReferenceHEl.getBoundingClientRect());
|
||||
}
|
||||
// 纵向参照
|
||||
if (rectifyReferenceV.value) {
|
||||
const rectifyReferenceVEl = getRectifyReferenceElement(rectifyReferenceV.value);
|
||||
({ bottom, top, y, height } = rectifyReferenceVEl.getBoundingClientRect());
|
||||
}
|
||||
return { top, left, right, bottom, height, width, x, y } as DOMRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否超出边界
|
||||
*/
|
||||
function isOverBounding(referenceBoundingRect: DOMRect, direct: string, value: number) {
|
||||
let overBound = false;
|
||||
let fixedValue = value;
|
||||
|
||||
if ((direct === 'left' || direct === 'top') && value <= referenceBoundingRect[direct]) {
|
||||
overBound = true;
|
||||
fixedValue = referenceBoundingRect[direct] + rectifyGutter;
|
||||
}
|
||||
if ((direct === 'right' || direct === 'bottom') && value >= referenceBoundingRect[direct]) {
|
||||
overBound = true;
|
||||
fixedValue = referenceBoundingRect[direct] - rectifyGutter;
|
||||
}
|
||||
return { overBound, fixedValue };
|
||||
}
|
||||
|
||||
function tryFixOverBound(
|
||||
placement: TooltipPlacement,
|
||||
originalPosition: RectPosition,
|
||||
referenceBoundingRect: DOMRect,
|
||||
originalBound: DOMRect,
|
||||
tooltipBound: DOMRect): RectPosition {
|
||||
let fixedLeft = originalPosition.left;
|
||||
let fixedTop = originalPosition.top;
|
||||
if (['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-righ'].includes(placement)) {
|
||||
const overLeftBound = isOverBounding(referenceBoundingRect, 'left', originalPosition.left);
|
||||
const overRightBound = isOverBounding(referenceBoundingRect, 'right', originalPosition.right);
|
||||
fixedLeft = overLeftBound.overBound ?
|
||||
overLeftBound.fixedValue :
|
||||
(overRightBound.overBound ? overRightBound.fixedValue - tooltipBound.width : originalPosition.left);
|
||||
}
|
||||
const overTopBound = isOverBounding(referenceBoundingRect, 'top', originalPosition.top);
|
||||
const overBottomBound = isOverBounding(referenceBoundingRect, 'bottom', originalPosition.top + originalBound.height);
|
||||
fixedTop = overTopBound.overBound ?
|
||||
overTopBound.fixedValue :
|
||||
(overBottomBound.overBound ? overBottomBound.fixedValue - tooltipBound.height : originalPosition.top);
|
||||
return { left: fixedLeft, top: fixedTop, right: originalPosition.right };
|
||||
}
|
||||
const { calculate } = useCalculatePosition(props, context);
|
||||
|
||||
const { adjustPlacement } = useAdjustPlacement(props, context);
|
||||
|
||||
const { adjustPosition } = useAdjustPosition(props, context);
|
||||
|
||||
const tooltipPlacement = computed<string>(() => {
|
||||
return placementAndAlignment.value.split('-')[0];
|
||||
});
|
||||
|
||||
const tooltipPosition = computed<RectPosition>(() => {
|
||||
const referenceBound = getReferenceBound();
|
||||
placementAndAlignment.value = autoRectifyDirection(
|
||||
placementAndAlignment.value, referenceBound, hostBound, tooltipBound, arrowBound);
|
||||
const originalTop = calculateTooltipTopPositoin(placementAndAlignment.value, hostBound, tooltipContentBound, arrowBound);
|
||||
const originalLeft = calculateTooltipLeftPosition(placementAndAlignment.value, hostBound, tooltipContentBound, arrowBound);
|
||||
const originalRight = calculateTooltipRightPosition(placementAndAlignment.value, hostBound, tooltipContentBound, arrowBound);
|
||||
const { top, left, right } = tryFixOverBound(
|
||||
placementAndAlignment.value,
|
||||
{ top: originalTop, left: originalLeft, right: originalRight },
|
||||
referenceBound,
|
||||
tooltipBound,
|
||||
tooltipBound
|
||||
);
|
||||
return { top: top + scrollTop, left: left + scrollLeft, right };
|
||||
const tooltipPosition = computed<TooltipPosition>(() => {
|
||||
const relativeRect = getRelativeElementBound();
|
||||
placementAndAlignment.value = adjustPlacement(placementAndAlignment.value, relativeRect, hostRect, tooltipRect, arrowRect);
|
||||
const originalPosition = calculate(placementAndAlignment.value, hostRect, tooltipRect, tooltipContentRect, arrowRect);
|
||||
const position = adjustPosition(placementAndAlignment.value, originalPosition, relativeRect, tooltipRect);
|
||||
return position;
|
||||
});
|
||||
|
||||
const arrowPosition = computed<RectPosition>(() => {
|
||||
const { top, left, right } = calculateArrowPosition(
|
||||
placementAndAlignment.value,
|
||||
hostBound,
|
||||
tooltipPosition.value,
|
||||
tooltipContentBound,
|
||||
arrowBound
|
||||
);
|
||||
return { top, left, right };
|
||||
});
|
||||
// const arrowPosition = computed<RectPosition>(() => {
|
||||
// const { top, left, right } = calculateArrowPosition(
|
||||
// placementAndAlignment.value,
|
||||
// hostBound,
|
||||
// tooltipPosition.value,
|
||||
// tooltipContentBound,
|
||||
// arrowBound
|
||||
// );
|
||||
// return { top, left, right };
|
||||
// });
|
||||
|
||||
return { arrowPosition, tooltipPlacement, tooltipPosition };
|
||||
return { tooltipPlacement, tooltipPosition };
|
||||
}
|
||||
|
|
|
@ -56,19 +56,19 @@ export default defineComponent({
|
|||
|
||||
onMounted(() => {
|
||||
if (arrowRef.value && tooltipRef.value && tooltipInnerRef.value && props.reference) {
|
||||
const { arrowPosition, tooltipPlacement, tooltipPosition } = useTooltipPosition(
|
||||
const { tooltipPlacement, tooltipPosition } = useTooltipPosition(
|
||||
props,
|
||||
context,
|
||||
props.reference.getBoundingClientRect(),
|
||||
tooltipInnerRef.value.getBoundingClientRect(),
|
||||
tooltipRef.value.getBoundingClientRect(),
|
||||
tooltipInnerRef.value.getBoundingClientRect(),
|
||||
arrowRef.value.getBoundingClientRect()
|
||||
);
|
||||
tooltipLeftPosition.value = `${tooltipPosition.value.left}px`;
|
||||
tooltipRightPosition.value = `${tooltipPosition.value.right}px`;
|
||||
tooltipTopPosition.value = `${tooltipPosition.value.top}px`;
|
||||
arrowLeftPosition.value = `${arrowPosition.value.left}px`;
|
||||
arrowTopPosition.value = `${arrowPosition.value.top}px`;
|
||||
tooltipLeftPosition.value = `${tooltipPosition.value.tooltip.left}px`;
|
||||
tooltipRightPosition.value = `${tooltipPosition.value.tooltip.right}px`;
|
||||
tooltipTopPosition.value = `${tooltipPosition.value.tooltip.top}px`;
|
||||
arrowLeftPosition.value = `${tooltipPosition.value.arrow.left}px`;
|
||||
arrowTopPosition.value = `${tooltipPosition.value.arrow.top}px`;
|
||||
placement.value = tooltipPlacement.value;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ export const tooltipProps = {
|
|||
type: Object as PropType<HTMLElement>,
|
||||
require: true,
|
||||
},
|
||||
referenceH: { type: String, defatul: "" },
|
||||
referenceV: { type: String, defatul: "" },
|
||||
horizontalRelative: { type: String, defatul: "" },
|
||||
verticalRelative: { type: String, defatul: "" },
|
||||
};
|
||||
export type TooltipProps = ExtractPropTypes<typeof tooltipProps>;
|
||||
|
|
Loading…
Reference in New Issue