From 3a2e018967d428d961c3fe66d6f98a196be0fd23 Mon Sep 17 00:00:00 2001 From: Sagi Date: Wed, 12 Oct 2022 15:56:08 +0800 Subject: [PATCH 1/7] feature(tooltip): add tooltip directive --- packages/ui-vue/components/tooltip/index.ts | 4 +- .../tooltip/src/tooltip.directive.tsx | 45 +++++++++++++++++++ .../components/tooltip/src/tooltip.props.ts | 3 +- packages/ui-vue/src/components/tooltip.vue | 3 ++ packages/ui-vue/src/main.ts | 5 ++- 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 packages/ui-vue/components/tooltip/src/tooltip.directive.tsx diff --git a/packages/ui-vue/components/tooltip/index.ts b/packages/ui-vue/components/tooltip/index.ts index ced280da74..0d59b2465a 100644 --- a/packages/ui-vue/components/tooltip/index.ts +++ b/packages/ui-vue/components/tooltip/index.ts @@ -1,12 +1,14 @@ import type { App } from 'vue'; import Tooltip from './src/tooltip.component'; +import TooltipDirective from './src/tooltip.directive'; export * from './src/tooltip.props'; -export { Tooltip }; +export { Tooltip, TooltipDirective }; export default { install(app: App): void { app.component(Tooltip.name, Tooltip); + app.directive('tooltip', TooltipDirective); } }; diff --git a/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx b/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx new file mode 100644 index 0000000000..12d4d687e2 --- /dev/null +++ b/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx @@ -0,0 +1,45 @@ +import { App, createApp, onUnmounted, reactive } from "vue"; +import { TooltipProps } from "./tooltip.props"; +import Tooltip from "./tooltip.component"; + +function initInstance(props: TooltipProps, content?: string): App { + const container = document.createElement('div'); + const app: App = createApp({ + setup() { + onUnmounted(() => { + document.body.removeChild(container); + }); + return () => ; + } + }); + document.body.appendChild(container); + app.mount(container); + return app; +} + +function showTooltip(options: TooltipProps): void { + const props: TooltipProps = reactive({ + ...options + }); + initInstance(props); +} + +const tooltipDirective = { + mounted: (element: HTMLElement, binding: Record, vnode: any) => { + element.addEventListener('mouseenter', ($event: MouseEvent) => { + $event.stopPropagation(); + showTooltip({ + content: 'This is a tooltip', + width: 100, + customClass: '', + position: 'top', + referenceBoundingRect: element.getBoundingClientRect() + }); + }); + element.addEventListener('mouseleave', ($event: MouseEvent) => { + $event.stopPropagation(); + }); + }, + unMounted: (element: HTMLElement, binding: Record, vnode: any) => { } +}; +export default tooltipDirective; diff --git a/packages/ui-vue/components/tooltip/src/tooltip.props.ts b/packages/ui-vue/components/tooltip/src/tooltip.props.ts index e71bdfb32e..aa6cb64b53 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.props.ts +++ b/packages/ui-vue/components/tooltip/src/tooltip.props.ts @@ -18,6 +18,7 @@ export const tooltipProps = { content: { type: String }, width: { type: Number }, customClass: { type: String }, - position: { type: String as PropType, default: 'top' } + position: { type: String as PropType, default: 'top' }, + referenceBoundingRect: { type: Object, default: {} } }; export type TooltipProps = ExtractPropTypes; diff --git a/packages/ui-vue/src/components/tooltip.vue b/packages/ui-vue/src/components/tooltip.vue index 336f8f7032..2a755618d7 100644 --- a/packages/ui-vue/src/components/tooltip.vue +++ b/packages/ui-vue/src/components/tooltip.vue @@ -1,7 +1,10 @@ diff --git a/packages/ui-vue/src/main.ts b/packages/ui-vue/src/main.ts index aa83e6bf0c..f051ba629a 100644 --- a/packages/ui-vue/src/main.ts +++ b/packages/ui-vue/src/main.ts @@ -1,5 +1,8 @@ import { createApp } from 'vue'; import './style.css'; import App from './app.vue'; +import Tooltip from '../components/tooltip/index'; + +const app = createApp(App); +app.use(Tooltip).mount('#app'); -createApp(App).mount('#app'); -- Gitee From 07bdfe1c38ad7e22df9280fc30710c00769048cf Mon Sep 17 00:00:00 2001 From: Sagi Date: Wed, 12 Oct 2022 19:26:11 +0800 Subject: [PATCH 2/7] feature(tooltip): add use-tooltip-position --- .../src/composition/use-tooltip-position.ts | 190 ++++++++++++++++++ .../tooltip/src/tooltip.component.tsx | 15 +- .../components/tooltip/src/tooltip.props.ts | 4 +- 3 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts diff --git a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts new file mode 100644 index 0000000000..51b7d55ee2 --- /dev/null +++ b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts @@ -0,0 +1,190 @@ +import { computed, SetupContext } from "vue"; +import { TooltipPlacement, TooltipProps } from "../tooltip.props"; + +export function useTooltipPosition(props: TooltipProps, context: SetupContext) { + + const space = 2; + + const tooltipPlacement = computed(() => { + return props.placement; + }); + + function calculateTooltipTopPositoin(placement: TooltipPlacement, hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { + let offsetY = 0; + const verticalAlignment = placement.indexOf('bottom') > -1 ? 'bottom' : (placement.indexOf('top') > -1 ? 'top' : 'middle'); + const originalPositionY = verticalAlignment === 'bottom' ? hostBound.bottom : hostBound.top; + + if (['top', 'top-left', 'top-right'].includes(placement)) { + offsetY = 0 - tooltipBound.height - space; + } else if (['left', 'right'].includes(placement)) { + offsetY = (hostBound.height - tooltipBound.height) / 2; + } else if (['bottom', 'bottom-left', 'bottom-right'].includes(placement)) { + offsetY = arrowBound.height + 2; + } else if (['right-bottom', 'left-bottom'].includes(placement)) { + offsetY = 0 - tooltipBound.height; + } else if (['left-top', 'right-top'].includes(placement)) { + offsetY = 0; + } else { + return 0; + } + + return originalPositionY + offsetY; + } + + function calculateTooltipLeftPosition(placement: TooltipPlacement, hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { + + let offsetX = 0; + const horizontalAlignment = placement.indexOf('right') > -1 ? 'right' : (placement.indexOf('left') > -1 ? 'left' : 'center'); + const originalPositionX = horizontalAlignment === 'right' ? hostBound.right : hostBound.left; + + if (['left', 'left-top', 'left-bottom'].includes(placement)) { + offsetX = 0 - tooltipBound.width - space; + } else if (['top', 'bottom'].includes(placement)) { + offsetX = (hostBound.width - tooltipBound.width) / 2; + } else if (['right', 'right-top', 'right-bottom'].includes(placement)) { + offsetX = space; + } else if (['top-right', 'bottom-right'].includes(placement)) { + offsetX = 0 - tooltipBound.width; + } else if (['top-left', 'bottom-left'].includes(placement)) { + offsetX = 0; + } else { + return 0; + } + + return originalPositionX + offsetX; + } + + function calculateTooltipRightPosition(placement: TooltipPlacement, hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { + let offsetX = 0; + const horizontalAlignment = placement.indexOf('right') > -1 ? 'right' : (placement.indexOf('left') > -1 ? 'left' : 'center'); + const originalPositionX = horizontalAlignment === 'right' ? hostBound.right : hostBound.left; + + if (['top', 'bottom'].includes(placement)) { + offsetX = (hostBound.width - tooltipBound.width) / 2 + tooltipBound.width; + } else if (['top-left', 'bottom-left'].includes(placement)) { + offsetX = tooltipBound.width; + } else if (['top-right', 'bottom-right'].includes(placement)) { + offsetX = 0; + } else { + return 0; + } + + return originalPositionX + offsetX; + } + + /* 计算tooltip最新位置 */ + function calculatePosition(hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { + let top = 0; + let left = 0; + let right = 0; + + switch (this.rectifyPlacement) { + case 'top': + top = hostPosition.top - tooltipSize.height - 2; + left = hostPosition.left + (hostPosition.width - tooltipSize.width) / 2; + right = left + tooltipSize.width; + break; + case 'left': + top = hostPosition.top + (hostPosition.height - tooltipSize.height) / 2; + left = hostPosition.left - tooltipSize.width - 2; + break; + case 'right': + top = hostPosition.top + (hostPosition.height - tooltipSize.height) / 2; + left = hostPosition.right + 2; + break; + case 'bottom': + top = hostPosition.bottom + arrowSize.height + 2; + left = hostPosition.left + (hostPosition.width - tooltipSize.width) / 2; + right = left + tooltipSize.width; + break; + case 'top-left': + top = hostPosition.top - tooltipSize.height - 2; + left = hostPosition.left; + right = left + tooltipSize.width; + break; + case 'top-right': + top = hostPosition.top - tooltipSize.height - 2; + left = hostPosition.right - tooltipSize.width; + right = hostPosition.right; + break; + case 'right-top': + top = hostPosition.top; + left = hostPosition.right + 2; + this.arrowNode.nativeElement.style.top = '10%'; + break; + case 'right-bottom': + top = hostPosition.bottom - tooltipSize.height; + left = hostPosition.right + 2; + this.arrowNode.nativeElement.style.bottom = '10%'; + break; + case 'bottom-left': + top = hostPosition.bottom + arrowSize.height + 2; + left = hostPosition.left; + right = left + tooltipSize.width; + break; + case 'bottom-right': + top = hostPosition.bottom + arrowSize.height + 2; + left = hostPosition.right - tooltipSize.width; + right = hostPosition.right; + break; + case 'left-top': + top = hostPosition.top; + left = hostPosition.left - tooltipSize.width - 2; + this.arrowNode.nativeElement.style.top = '10%'; + break; + case 'left-bottom': + top = hostPosition.bottom - tooltipSize.height; + left = hostPosition.left - tooltipSize.width - 2; + this.arrowNode.nativeElement.style.bottom = '10%'; + } + let overResult; + let arrowLeft = 0; + let arrowTop = 0 + switch (this.rectifyPlacement) { + case 'top': + case 'top-left': + case 'top-right': + case 'bottom': + case 'bottom-left': + case 'bottom-right': + overResult = this.isOverBounding('left', left); + if (overResult.isOver) { + left = overResult.newValue; + } else { + overResult = this.isOverBounding('right', right); + if (overResult.isOver) { + left = overResult.newValue - tooltipSize.width; + } + } + arrowLeft = left - hostPosition.left - hostPosition.width * 0.5 + arrowSize.width * 0.5; + if (this.rectifyPlacement.indexOf('-left') > 0) { + arrowLeft += hostPosition.width * 0.4; + } else if (this.rectifyPlacement.indexOf('-right') > 0) { + arrowLeft -= hostPosition.width * 0.4; + } + this.arrowNode.nativeElement.style.left = Math.abs(arrowLeft) + 'px'; + break; + default: + overResult = this.isOverBounding('top', top); + + if (overResult.isOver) { + top = overResult.newValue; + } else { + overResult = this.isOverBounding('bottom', bottom); + if (overResult.isOver) { + top = overResult.newValue - tooltipSize.height; + } + } + arrowTop = top - hostPosition.top - hostPosition.height * 0.5 + arrowSize.height * 0.5;; + if (this.rectifyPlacement.indexOf('-top') > 0) { + arrowTop += hostPosition.height * 0.4; + } else if (this.rectifyPlacement.indexOf('-bottom') > 0) { + arrowTop -= hostPosition.height * 0.4; + } + this.arrowNode.nativeElement.style.top = Math.abs(arrowTop) + 'px'; + } + this.tooltipNode.nativeElement.style.top = top + 'px'; + this.tooltipNode.nativeElement.style.left = left + 'px'; + + } +} diff --git a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx index 625e74688c..3ef509136f 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx +++ b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx @@ -10,10 +10,15 @@ export default defineComponent({ setup(props: TooltipProps, context: SetupContext) { const isTextContext = ref(true); - const tooltipClass = computed(() => ({ - tooltip: true, - show: true - })); + const tooltipClass = computed(() => { + const classObject = { + tooltip: true, + show: true + } as any; + const tooltipClassName = `bs-tooltip-${props.position}`; + classObject[tooltipClassName] = true; + return classObject; + }); const shouldShowTooltipText = computed(() => isTextContext.value); @@ -21,7 +26,7 @@ export default defineComponent({ return () => { return ( -
+
diff --git a/packages/ui-vue/components/tooltip/src/tooltip.props.ts b/packages/ui-vue/components/tooltip/src/tooltip.props.ts index aa6cb64b53..a88a60bf48 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.props.ts +++ b/packages/ui-vue/components/tooltip/src/tooltip.props.ts @@ -1,6 +1,6 @@ import { ExtractPropTypes, PropType } from 'vue'; -export type TooltipPosition = +export type TooltipPlacement = | 'top' | 'top-left' | 'top-right' @@ -18,7 +18,7 @@ export const tooltipProps = { content: { type: String }, width: { type: Number }, customClass: { type: String }, - position: { type: String as PropType, default: 'top' }, + placement: { type: String as PropType, default: 'top' }, referenceBoundingRect: { type: Object, default: {} } }; export type TooltipProps = ExtractPropTypes; -- Gitee From 26bbfaea20aab86a1fd3d12c80c5d2116f5fa7ca Mon Sep 17 00:00:00 2001 From: Sagi Date: Wed, 12 Oct 2022 20:55:32 +0800 Subject: [PATCH 3/7] feature(tooltip): implement calculate tooltip position --- .../src/composition/use-tooltip-position.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts index 51b7d55ee2..5eec9cd1db 100644 --- a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts +++ b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts @@ -5,6 +5,8 @@ export function useTooltipPosition(props: TooltipProps, context: SetupContext) { const space = 2; + const rectifyGutter = 20; + const tooltipPlacement = computed(() => { return props.placement; }); @@ -72,6 +74,64 @@ export function useTooltipPosition(props: TooltipProps, context: SetupContext) { return originalPositionX + offsetX; } + /** + * 判断是否超出边界 + */ + function isOverBounding(referenceBoundingRect: DOMRect, direct: string, value: number) { + let overBound = false; + let fixedValue = 0; + + 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, + referenceBoundingRect: DOMRect, + originalBound: DOMRect, + tooltipBound: DOMRect): DOMRect { + let fixedLeft = originalBound.left; + let fixedTop = originalBound.top; + if (['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-righ'].includes(placement)) { + const overLeftBound = isOverBounding(referenceBoundingRect, 'left', originalBound.left); + const overRightBound = isOverBounding(referenceBoundingRect, 'right', originalBound.right); + fixedLeft = overLeftBound.overBound ? + overLeftBound.fixedValue : + (overRightBound.overBound ? overRightBound.fixedValue - tooltipBound.width : originalBound.left); + } + const overTopBound = isOverBounding(referenceBoundingRect, 'top', originalBound.top); + const overBottomBound = isOverBounding(referenceBoundingRect, 'bottom', originalBound.bottom); + fixedTop = overTopBound.overBound ? + overTopBound.fixedValue : + (overBottomBound.overBound ? overBottomBound.fixedValue - tooltipBound.height : originalBound.top); + return Object.assign({}, originalBound, { left: fixedLeft, top: fixedTop }); + } + + function calculateArrowPosition( + placement: TooltipPlacement, + hostBound: DOMRect, + fixedTooltipBound: DOMRect, + arrowBound: DOMRect): DOMRect { + const leftAlignment = placement.indexOf('-left') > 0; + const rightAlignment = placement.indexOf('-right') > 0; + const topAlignment = placement.indexOf('-top') > 0; + const bottomAlignment = placement.indexOf('-bottom') > 0; + const offsetX = leftAlignment ? hostBound.width * 0.4 : (rightAlignment ? 0 - hostBound.width * 0.4 : 0); + const offsetY = topAlignment ? hostBound.height * 0.4 : (bottomAlignment ? 0 - hostBound.height * 0.4 : 0); + + const fixedArrowLeft = fixedTooltipBound.left - hostBound.left - hostBound.width * 0.5 + arrowBound.width * 0.5 + offsetX; + const fixedArrowTop = fixedTooltipBound.top - hostBound.top - hostBound.height * 0.5 + arrowBound.height * 0.5 + offsetY; + + return Object.assign({}, arrowBound, { left: fixedArrowLeft, top: fixedArrowTop }); + } + /* 计算tooltip最新位置 */ function calculatePosition(hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { let top = 0; -- Gitee From 436787006191186a00d9fc8b3504a91cce0bb166 Mon Sep 17 00:00:00 2001 From: Sagi Date: Thu, 13 Oct 2022 11:40:56 +0800 Subject: [PATCH 4/7] feature(tooltip): implement calculate tooltip position and tooltip arrow position --- .../src/composition/use-tooltip-position.ts | 72 +++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts index 5eec9cd1db..4eab6b754d 100644 --- a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts +++ b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts @@ -1,16 +1,63 @@ -import { computed, SetupContext } from "vue"; +import { computed, ref, SetupContext } from "vue"; import { TooltipPlacement, TooltipProps } from "../tooltip.props"; -export function useTooltipPosition(props: TooltipProps, context: SetupContext) { +type RectDirection = 'top' | 'bottom' | 'right' | 'left'; +type RectSizeProperty = 'height' | 'width'; +interface RectPosition { + top: number; + left: number; + right: number; +} + +export function useTooltipPosition( + props: TooltipProps, + context: SetupContext, + hostBound: DOMRect, + tooltipBound: DOMRect, + arrowBound: DOMRect) { const space = 2; const rectifyGutter = 20; + const revertDirectionMap = new Map( + [['top', 'bottom'], ['bottom', 'top'], ['left', 'right'], ['right', 'left']] + ); + + const directionBoundMap = new Map( + [['top', 'height'], ['bottom', 'height'], ['left', 'width'], ['right', 'width']] + ); + + const placement = ref(props.placement); + const tooltipPlacement = computed(() => { return props.placement; }); + function revertPlacement(placement: string, direction: RectDirection) { + const revertDirection: RectDirection = revertDirectionMap.get(direction) || direction; + const revertedPlacement = placement.replace(direction, revertDirection); + return revertDirection; + } + + 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 calculateTooltipTopPositoin(placement: TooltipPlacement, hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { let offsetY = 0; const verticalAlignment = placement.indexOf('bottom') > -1 ? 'bottom' : (placement.indexOf('top') > -1 ? 'top' : 'middle'); @@ -94,9 +141,10 @@ export function useTooltipPosition(props: TooltipProps, context: SetupContext) { function tryFixOverBound( placement: TooltipPlacement, + originalPosition: RectPosition, referenceBoundingRect: DOMRect, originalBound: DOMRect, - tooltipBound: DOMRect): DOMRect { + tooltipBound: DOMRect): RectPosition { let fixedLeft = originalBound.left; let fixedTop = originalBound.top; if (['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-righ'].includes(placement)) { @@ -111,7 +159,7 @@ export function useTooltipPosition(props: TooltipProps, context: SetupContext) { fixedTop = overTopBound.overBound ? overTopBound.fixedValue : (overBottomBound.overBound ? overBottomBound.fixedValue - tooltipBound.height : originalBound.top); - return Object.assign({}, originalBound, { left: fixedLeft, top: fixedTop }); + return { left: fixedLeft, top: fixedTop, right: originalPosition.right }; } function calculateArrowPosition( @@ -129,9 +177,23 @@ export function useTooltipPosition(props: TooltipProps, context: SetupContext) { const fixedArrowLeft = fixedTooltipBound.left - hostBound.left - hostBound.width * 0.5 + arrowBound.width * 0.5 + offsetX; const fixedArrowTop = fixedTooltipBound.top - hostBound.top - hostBound.height * 0.5 + arrowBound.height * 0.5 + offsetY; - return Object.assign({}, arrowBound, { left: fixedArrowLeft, top: fixedArrowTop }); + return Object.assign({}, arrowBound, { left: Math.abs(fixedArrowLeft), top: Math.abs(fixedArrowTop) }); } + const tooltipPosition = computed(() => { + const originalTop = calculateTooltipTopPositoin(placement.value, hostBound, tooltipBound, arrowBound); + const originalLeft = calculateTooltipLeftPosition(placement.value, hostBound, tooltipBound, arrowBound); + const originalRight = calculateTooltipRightPosition(placement.value, hostBound, tooltipBound, arrowBound); + const { top, left, right } = tryFixOverBound( + placement.value, + { top: originalTop, left: originalLeft, right: originalRight }, + hostBound, + tooltipBound, + tooltipBound + ); + return { top, left, right }; + }); + /* 计算tooltip最新位置 */ function calculatePosition(hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { let top = 0; -- Gitee From 7a107b1f0627c969d65b826d633f497e3d374019 Mon Sep 17 00:00:00 2001 From: Sagi Date: Thu, 13 Oct 2022 11:59:36 +0800 Subject: [PATCH 5/7] refactor(tooltip): refactor tooltip types --- .../tooltip/src/composition/types.ts | 16 ++ .../src/composition/use-tooltip-position.ts | 153 +++--------------- .../tooltip/src/tooltip.component.tsx | 2 +- .../tooltip/src/tooltip.directive.tsx | 2 +- 4 files changed, 37 insertions(+), 136 deletions(-) diff --git a/packages/ui-vue/components/tooltip/src/composition/types.ts b/packages/ui-vue/components/tooltip/src/composition/types.ts index e69de29bb2..f848d81a19 100644 --- a/packages/ui-vue/components/tooltip/src/composition/types.ts +++ b/packages/ui-vue/components/tooltip/src/composition/types.ts @@ -0,0 +1,16 @@ +import { ComputedRef } from "vue"; +import { TooltipPlacement } from "../tooltip.props"; + +export type RectDirection = 'top' | 'bottom' | 'right' | 'left'; +export type RectSizeProperty = 'height' | 'width'; +export interface RectPosition { + top: number; + left: number; + right: number; +} + +export interface UseTooltipPosition { + tooltipPlacement: ComputedRef; + tooltipPosition: ComputedRef; + arrowPosition: ComputedRef; +} diff --git a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts index 4eab6b754d..af578f78a2 100644 --- a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts +++ b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts @@ -1,13 +1,6 @@ import { computed, ref, SetupContext } from "vue"; import { TooltipPlacement, TooltipProps } from "../tooltip.props"; - -type RectDirection = 'top' | 'bottom' | 'right' | 'left'; -type RectSizeProperty = 'height' | 'width'; -interface RectPosition { - top: number; - left: number; - right: number; -} +import { RectDirection, RectPosition, RectSizeProperty } from "./types"; export function useTooltipPosition( props: TooltipProps, @@ -30,14 +23,10 @@ export function useTooltipPosition( const placement = ref(props.placement); - const tooltipPlacement = computed(() => { - return props.placement; - }); - - function revertPlacement(placement: string, direction: RectDirection) { + function revertPlacement(placement: string, direction: RectDirection): TooltipPlacement { const revertDirection: RectDirection = revertDirectionMap.get(direction) || direction; - const revertedPlacement = placement.replace(direction, revertDirection); - return revertDirection; + const revertedPlacement = placement.replace(direction, revertDirection) as TooltipPlacement; + return revertedPlacement; } function autoRectifyDirection( @@ -165,8 +154,8 @@ export function useTooltipPosition( function calculateArrowPosition( placement: TooltipPlacement, hostBound: DOMRect, - fixedTooltipBound: DOMRect, - arrowBound: DOMRect): DOMRect { + tooltipPositoin: RectPosition, + arrowBound: DOMRect): RectPosition { const leftAlignment = placement.indexOf('-left') > 0; const rightAlignment = placement.indexOf('-right') > 0; const topAlignment = placement.indexOf('-top') > 0; @@ -174,12 +163,17 @@ export function useTooltipPosition( const offsetX = leftAlignment ? hostBound.width * 0.4 : (rightAlignment ? 0 - hostBound.width * 0.4 : 0); const offsetY = topAlignment ? hostBound.height * 0.4 : (bottomAlignment ? 0 - hostBound.height * 0.4 : 0); - const fixedArrowLeft = fixedTooltipBound.left - hostBound.left - hostBound.width * 0.5 + arrowBound.width * 0.5 + offsetX; - const fixedArrowTop = fixedTooltipBound.top - hostBound.top - hostBound.height * 0.5 + arrowBound.height * 0.5 + offsetY; + const fixedArrowLeft = tooltipPositoin.left - hostBound.left - hostBound.width * 0.5 + arrowBound.width * 0.5 + offsetX; + const fixedArrowTop = tooltipPositoin.top - hostBound.top - hostBound.height * 0.5 + arrowBound.height * 0.5 + offsetY; - return Object.assign({}, arrowBound, { left: Math.abs(fixedArrowLeft), top: Math.abs(fixedArrowTop) }); + return { left: Math.abs(fixedArrowLeft), top: Math.abs(fixedArrowTop), right: 0 }; } + const tooltipPlacement = computed(() => { + const rectifyPlacement = autoRectifyDirection(placement.value, hostBound, hostBound, tooltipBound, arrowBound); + return rectifyPlacement; + }); + const tooltipPosition = computed(() => { const originalTop = calculateTooltipTopPositoin(placement.value, hostBound, tooltipBound, arrowBound); const originalLeft = calculateTooltipLeftPosition(placement.value, hostBound, tooltipBound, arrowBound); @@ -194,119 +188,10 @@ export function useTooltipPosition( return { top, left, right }; }); - /* 计算tooltip最新位置 */ - function calculatePosition(hostBound: DOMRect, tooltipBound: DOMRect, arrowBound: DOMRect) { - let top = 0; - let left = 0; - let right = 0; - - switch (this.rectifyPlacement) { - case 'top': - top = hostPosition.top - tooltipSize.height - 2; - left = hostPosition.left + (hostPosition.width - tooltipSize.width) / 2; - right = left + tooltipSize.width; - break; - case 'left': - top = hostPosition.top + (hostPosition.height - tooltipSize.height) / 2; - left = hostPosition.left - tooltipSize.width - 2; - break; - case 'right': - top = hostPosition.top + (hostPosition.height - tooltipSize.height) / 2; - left = hostPosition.right + 2; - break; - case 'bottom': - top = hostPosition.bottom + arrowSize.height + 2; - left = hostPosition.left + (hostPosition.width - tooltipSize.width) / 2; - right = left + tooltipSize.width; - break; - case 'top-left': - top = hostPosition.top - tooltipSize.height - 2; - left = hostPosition.left; - right = left + tooltipSize.width; - break; - case 'top-right': - top = hostPosition.top - tooltipSize.height - 2; - left = hostPosition.right - tooltipSize.width; - right = hostPosition.right; - break; - case 'right-top': - top = hostPosition.top; - left = hostPosition.right + 2; - this.arrowNode.nativeElement.style.top = '10%'; - break; - case 'right-bottom': - top = hostPosition.bottom - tooltipSize.height; - left = hostPosition.right + 2; - this.arrowNode.nativeElement.style.bottom = '10%'; - break; - case 'bottom-left': - top = hostPosition.bottom + arrowSize.height + 2; - left = hostPosition.left; - right = left + tooltipSize.width; - break; - case 'bottom-right': - top = hostPosition.bottom + arrowSize.height + 2; - left = hostPosition.right - tooltipSize.width; - right = hostPosition.right; - break; - case 'left-top': - top = hostPosition.top; - left = hostPosition.left - tooltipSize.width - 2; - this.arrowNode.nativeElement.style.top = '10%'; - break; - case 'left-bottom': - top = hostPosition.bottom - tooltipSize.height; - left = hostPosition.left - tooltipSize.width - 2; - this.arrowNode.nativeElement.style.bottom = '10%'; - } - let overResult; - let arrowLeft = 0; - let arrowTop = 0 - switch (this.rectifyPlacement) { - case 'top': - case 'top-left': - case 'top-right': - case 'bottom': - case 'bottom-left': - case 'bottom-right': - overResult = this.isOverBounding('left', left); - if (overResult.isOver) { - left = overResult.newValue; - } else { - overResult = this.isOverBounding('right', right); - if (overResult.isOver) { - left = overResult.newValue - tooltipSize.width; - } - } - arrowLeft = left - hostPosition.left - hostPosition.width * 0.5 + arrowSize.width * 0.5; - if (this.rectifyPlacement.indexOf('-left') > 0) { - arrowLeft += hostPosition.width * 0.4; - } else if (this.rectifyPlacement.indexOf('-right') > 0) { - arrowLeft -= hostPosition.width * 0.4; - } - this.arrowNode.nativeElement.style.left = Math.abs(arrowLeft) + 'px'; - break; - default: - overResult = this.isOverBounding('top', top); - - if (overResult.isOver) { - top = overResult.newValue; - } else { - overResult = this.isOverBounding('bottom', bottom); - if (overResult.isOver) { - top = overResult.newValue - tooltipSize.height; - } - } - arrowTop = top - hostPosition.top - hostPosition.height * 0.5 + arrowSize.height * 0.5;; - if (this.rectifyPlacement.indexOf('-top') > 0) { - arrowTop += hostPosition.height * 0.4; - } else if (this.rectifyPlacement.indexOf('-bottom') > 0) { - arrowTop -= hostPosition.height * 0.4; - } - this.arrowNode.nativeElement.style.top = Math.abs(arrowTop) + 'px'; - } - this.tooltipNode.nativeElement.style.top = top + 'px'; - this.tooltipNode.nativeElement.style.left = left + 'px'; + const arrowPosition = computed(() => { + const { top, left, right } = calculateArrowPosition(placement.value, hostBound, tooltipPosition.value, arrowBound); + return { top, left, right }; + }); - } + return { arrowPosition, tooltipPlacement, tooltipPosition }; } diff --git a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx index 3ef509136f..894e160032 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx +++ b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx @@ -15,7 +15,7 @@ export default defineComponent({ tooltip: true, show: true } as any; - const tooltipClassName = `bs-tooltip-${props.position}`; + const tooltipClassName = `bs-tooltip-${props.placement}`; classObject[tooltipClassName] = true; return classObject; }); diff --git a/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx b/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx index 12d4d687e2..e86e585da9 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx +++ b/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx @@ -32,7 +32,7 @@ const tooltipDirective = { content: 'This is a tooltip', width: 100, customClass: '', - position: 'top', + placement: 'top', referenceBoundingRect: element.getBoundingClientRect() }); }); -- Gitee From 1a7999ea58f2d4d178cad750e1960954bc250238 Mon Sep 17 00:00:00 2001 From: Sagi Date: Thu, 13 Oct 2022 14:39:10 +0800 Subject: [PATCH 6/7] fix(tooltip): support change tooltip position --- .../tooltip/src/tooltip.component.tsx | 44 ++++++++++++++++--- .../tooltip/src/tooltip.directive.tsx | 4 +- .../components/tooltip/src/tooltip.props.ts | 5 ++- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx index 894e160032..69033e6307 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx +++ b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx @@ -1,7 +1,8 @@ -import { computed, defineComponent, ref, SetupContext } from 'vue'; +import { computed, defineComponent, onMounted, ref, SetupContext } from 'vue'; import { TooltipProps, tooltipProps } from './tooltip.props'; import './tooltip.css'; +import { useTooltipPosition } from './composition/use-tooltip-position'; export default defineComponent({ name: 'FTooltip', @@ -9,13 +10,15 @@ export default defineComponent({ emits: [], setup(props: TooltipProps, context: SetupContext) { const isTextContext = ref(true); - + const arrowRef = ref(); + const tooltipRef = ref(); + const placement = ref(props.placement); const tooltipClass = computed(() => { const classObject = { tooltip: true, show: true } as any; - const tooltipClassName = `bs-tooltip-${props.placement}`; + const tooltipClassName = `bs-tooltip-${placement.value}`; classObject[tooltipClassName] = true; return classObject; }); @@ -24,10 +27,41 @@ export default defineComponent({ const tooltipText = computed(() => props.content); + const tooltipLeftPosition = ref(''); + + const tooltipTopPosition = ref(''); + + const tooltipRightPosition = ref(''); + + const tooltipStyle = computed(() => { + const styleObject = { + left: tooltipLeftPosition.value, + top: tooltipTopPosition.value, + right: tooltipRightPosition.value + }; + return styleObject; + }); + + onMounted(() => { + if (arrowRef.value && tooltipRef.value && props.reference) { + const { arrowPosition, tooltipPlacement, tooltipPosition } = useTooltipPosition( + props, + context, + props.reference.getBoundingClientRect(), + tooltipRef.value.getBoundingClientRect(), + arrowRef.value.getBoundingClientRect() + ); + placement.value = tooltipPlacement.value; + tooltipLeftPosition.value = `${tooltipPosition.value.left}px`; + tooltipRightPosition.value = `${tooltipPosition.value.right}px`; + tooltipTopPosition.value = `${tooltipPosition.value.top}px`; + } + }); + return () => { return ( -
-
+
+
{shouldShowTooltipText.value &&
} diff --git a/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx b/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx index e86e585da9..68ab085852 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx +++ b/packages/ui-vue/components/tooltip/src/tooltip.directive.tsx @@ -9,7 +9,7 @@ function initInstance(props: TooltipProps, content?: string): App { onUnmounted(() => { document.body.removeChild(container); }); - return () => ; + return () => ; } }); document.body.appendChild(container); @@ -33,7 +33,7 @@ const tooltipDirective = { width: 100, customClass: '', placement: 'top', - referenceBoundingRect: element.getBoundingClientRect() + reference: element }); }); element.addEventListener('mouseleave', ($event: MouseEvent) => { diff --git a/packages/ui-vue/components/tooltip/src/tooltip.props.ts b/packages/ui-vue/components/tooltip/src/tooltip.props.ts index a88a60bf48..fbfe02494c 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.props.ts +++ b/packages/ui-vue/components/tooltip/src/tooltip.props.ts @@ -19,6 +19,9 @@ export const tooltipProps = { width: { type: Number }, customClass: { type: String }, placement: { type: String as PropType, default: 'top' }, - referenceBoundingRect: { type: Object, default: {} } + reference: { + type: Object as PropType, + require: true, + } }; export type TooltipProps = ExtractPropTypes; -- Gitee From 3f71272584ede4ddecfbbb6badb601e5c169f1b6 Mon Sep 17 00:00:00 2001 From: Sagi Date: Thu, 13 Oct 2022 15:35:42 +0800 Subject: [PATCH 7/7] fix(tooltip): fix tooltip position with scroll height and scroll width --- .../tooltip/src/composition/use-tooltip-position.ts | 6 ++++-- .../ui-vue/components/tooltip/src/tooltip.component.tsx | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts index af578f78a2..79778f8fa9 100644 --- a/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts +++ b/packages/ui-vue/components/tooltip/src/composition/use-tooltip-position.ts @@ -13,6 +13,8 @@ export function useTooltipPosition( const rectifyGutter = 20; + const { scrollLeft, scrollTop } = document.documentElement; + const revertDirectionMap = new Map( [['top', 'bottom'], ['bottom', 'top'], ['left', 'right'], ['right', 'left']] ); @@ -185,12 +187,12 @@ export function useTooltipPosition( tooltipBound, tooltipBound ); - return { top, left, right }; + return { top: top + scrollTop, left: left + scrollLeft, right }; }); const arrowPosition = computed(() => { const { top, left, right } = calculateArrowPosition(placement.value, hostBound, tooltipPosition.value, arrowBound); - return { top, left, right }; + return { top: top + scrollTop, left: left + scrollLeft, right }; }); return { arrowPosition, tooltipPlacement, tooltipPosition }; diff --git a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx index 69033e6307..04cdf7297b 100644 --- a/packages/ui-vue/components/tooltip/src/tooltip.component.tsx +++ b/packages/ui-vue/components/tooltip/src/tooltip.component.tsx @@ -36,8 +36,7 @@ export default defineComponent({ const tooltipStyle = computed(() => { const styleObject = { left: tooltipLeftPosition.value, - top: tooltipTopPosition.value, - right: tooltipRightPosition.value + top: tooltipTopPosition.value }; return styleObject; }); -- Gitee