|
@@ -133,11 +133,26 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
unBindResizeEvent() {
|
|
|
this._adapter.unregisterResizeHandler(this.onResize);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
removePortal = () => {
|
|
|
this._adapter.removePortal();
|
|
|
}
|
|
|
|
|
|
+ _adjustPos(position = '', isVertical = false, adjustType = 'reverse', concatPos?: any) {
|
|
|
+ switch (adjustType) {
|
|
|
+ case 'reverse':
|
|
|
+ return this._reversePos(position, isVertical);
|
|
|
+ case 'expand':
|
|
|
+ // only happens when position is top/bottom/left/right
|
|
|
+ return this._expandPos(position, concatPos);
|
|
|
+ case 'reduce':
|
|
|
+ // only happens when position other than top/bottom/left/right
|
|
|
+ return this._reducePos(position);
|
|
|
+ default:
|
|
|
+ return this._reversePos(position, isVertical);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
_reversePos(position = '', isVertical = false) {
|
|
|
if (isVertical) {
|
|
|
if (REGS.TOP.test(position)) {
|
|
@@ -153,6 +168,16 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
return position;
|
|
|
}
|
|
|
|
|
|
+ _expandPos(position = '', concatPos: string) {
|
|
|
+ return position.concat(concatPos);
|
|
|
+ }
|
|
|
+
|
|
|
+ _reducePos(position = '') {
|
|
|
+ // if cur position consists of two directions, remove the last position
|
|
|
+ const found = ['Top', 'Bottom', 'Left', 'Right'].find(pos => position.endsWith(pos));
|
|
|
+ return found ? position.replace(found, ''): position;
|
|
|
+ }
|
|
|
+
|
|
|
clearDelayTimer() {
|
|
|
if (this._timer) {
|
|
|
clearTimeout(this._timer);
|
|
@@ -269,6 +294,10 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
const content = this.getProp('content');
|
|
|
const trigger = this.getProp('trigger');
|
|
|
const clickTriggerToHide = this.getProp('clickTriggerToHide');
|
|
|
+ const { visible } = this.getStates();
|
|
|
+ if (visible) {
|
|
|
+ return ;
|
|
|
+ }
|
|
|
|
|
|
this.clearDelayTimer();
|
|
|
|
|
@@ -285,9 +314,7 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
this._togglePortalVisible(true);
|
|
|
});
|
|
|
|
|
|
- const position = this.calcPosition(null, null, null, false);
|
|
|
-
|
|
|
- this._adapter.insertPortal(content, position);
|
|
|
+ this._adapter.insertPortal(content, { left: -9999, top: -9999 }); // offscreen rendering
|
|
|
|
|
|
if (trigger === 'custom') {
|
|
|
// eslint-disable-next-line
|
|
@@ -358,14 +385,15 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- calcPosStyle(triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect, position?: Position, spacing?: number) {
|
|
|
- triggerRect = (isEmpty(triggerRect) ? triggerRect : this._adapter.getTriggerBounding()) || { ...defaultRect as any };
|
|
|
- containerRect = (isEmpty(containerRect) ? containerRect : this._adapter.getPopupContainerRect()) || {
|
|
|
+ calcPosStyle(props: {triggerRect: DOMRect; wrapperRect: DOMRect; containerRect: PopupContainerDOMRect; position?: Position; spacing?: number; isOverFlow?: [boolean, boolean]}) {
|
|
|
+ const { spacing, isOverFlow } = props;
|
|
|
+ const triggerRect = (isEmpty(props.triggerRect) ? props.triggerRect : this._adapter.getTriggerBounding()) || { ...defaultRect as any };
|
|
|
+ const containerRect = (isEmpty(props.containerRect) ? props.containerRect : this._adapter.getPopupContainerRect()) || {
|
|
|
...defaultRect,
|
|
|
};
|
|
|
- wrapperRect = (isEmpty(wrapperRect) ? wrapperRect : this._adapter.getWrapperBounding()) || { ...defaultRect as any };
|
|
|
+ const wrapperRect = (isEmpty(props.wrapperRect) ? props.wrapperRect : this._adapter.getWrapperBounding()) || { ...defaultRect as any };
|
|
|
// eslint-disable-next-line
|
|
|
- position = position != null ? position : this.getProp('position');
|
|
|
+ const position = props.position != null ? props.position : this.getProp('position');
|
|
|
// eslint-disable-next-line
|
|
|
const SPACING = spacing != null ? spacing : this.getProp('spacing');
|
|
|
const { arrowPointAtCenter, showArrow, arrowBounding } = this.getProps();
|
|
@@ -389,67 +417,104 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
const offsetXWithArrow = positionOffsetX + horizontalArrowWidth / 2;
|
|
|
const offsetYWithArrow = positionOffsetY + verticalArrowHeight / 2;
|
|
|
|
|
|
+ const heightDifference = wrapperRect.height - containerRect.height;
|
|
|
+ const widthDifference = wrapperRect.width - containerRect.width;
|
|
|
+
|
|
|
+ const offsetHeight = heightDifference > 0 ? heightDifference : 0;
|
|
|
+ const offsetWidth = widthDifference > 0 ? widthDifference : 0;
|
|
|
+ const isHeightOverFlow = isOverFlow && isOverFlow[0];
|
|
|
+ const isWidthOverFlow = isOverFlow && isOverFlow[1];
|
|
|
+
|
|
|
+ const isTriggerNearLeft = middleX - containerRect.left < containerRect.right - middleX;
|
|
|
+ const isTriggerNearTop = middleY - containerRect.top < containerRect.bottom - middleY;
|
|
|
+
|
|
|
+
|
|
|
switch (position) {
|
|
|
case 'top':
|
|
|
- left = middleX;
|
|
|
- top = triggerRect.top - SPACING;
|
|
|
+ // left = middleX;
|
|
|
+ // top = triggerRect.top - SPACING;
|
|
|
+ left = isWidthOverFlow ? (isTriggerNearLeft ? containerRect.left + wrapperRect.width / 2 : containerRect.right - wrapperRect.width / 2 + offsetWidth): middleX;
|
|
|
+ top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
|
|
|
translateX = -0.5;
|
|
|
translateY = -1;
|
|
|
break;
|
|
|
case 'topLeft':
|
|
|
- left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
|
|
|
- top = triggerRect.top - SPACING;
|
|
|
+ // left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
|
|
|
+ // top = triggerRect.top - SPACING;
|
|
|
+ left = isWidthOverFlow ? containerRect.left : (pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left);
|
|
|
+ top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
|
|
|
translateY = -1;
|
|
|
break;
|
|
|
case 'topRight':
|
|
|
- left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
|
|
|
- top = triggerRect.top - SPACING;
|
|
|
+ // left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
|
|
|
+ // top = triggerRect.top - SPACING;
|
|
|
+ left = isWidthOverFlow ? containerRect.right + offsetWidth : (pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right);
|
|
|
+ top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
|
|
|
translateY = -1;
|
|
|
translateX = -1;
|
|
|
break;
|
|
|
case 'left':
|
|
|
- left = triggerRect.left - SPACING;
|
|
|
- top = middleY;
|
|
|
+ // left = triggerRect.left - SPACING;
|
|
|
+ // top = middleY;
|
|
|
+ // left = isWidthOverFlow? containerRect.right - SPACING : triggerRect.left - SPACING;
|
|
|
+ left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow : triggerRect.left - SPACING;
|
|
|
+ top = isHeightOverFlow ? (isTriggerNearTop ? containerRect.top + wrapperRect.height / 2 : containerRect.bottom - wrapperRect.height / 2 + offsetHeight): middleY;
|
|
|
translateX = -1;
|
|
|
translateY = -0.5;
|
|
|
break;
|
|
|
case 'leftTop':
|
|
|
- left = triggerRect.left - SPACING;
|
|
|
- top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
|
|
|
+ // left = triggerRect.left - SPACING;
|
|
|
+ // top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
|
|
|
+ left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow : triggerRect.left - SPACING;
|
|
|
+ top = isHeightOverFlow ? containerRect.top : (pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top);
|
|
|
translateX = -1;
|
|
|
break;
|
|
|
case 'leftBottom':
|
|
|
- left = triggerRect.left - SPACING;
|
|
|
- top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
|
|
|
+ // left = triggerRect.left - SPACING;
|
|
|
+ // top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
|
|
|
+ left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow: triggerRect.left - SPACING;
|
|
|
+ top = isHeightOverFlow ? containerRect.bottom + offsetHeight: (pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom);
|
|
|
translateX = -1;
|
|
|
translateY = -1;
|
|
|
break;
|
|
|
case 'bottom':
|
|
|
- left = middleX;
|
|
|
- top = triggerRect.top + triggerRect.height + SPACING;
|
|
|
+ // left = middleX;
|
|
|
+ // top = triggerRect.top + triggerRect.height + SPACING;
|
|
|
+ left = isWidthOverFlow ? (isTriggerNearLeft ? containerRect.left + wrapperRect.width / 2 : containerRect.right - wrapperRect.width / 2 + offsetWidth): middleX;
|
|
|
+ top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING: triggerRect.top + triggerRect.height + SPACING;
|
|
|
translateX = -0.5;
|
|
|
break;
|
|
|
case 'bottomLeft':
|
|
|
- left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
|
|
|
- top = triggerRect.bottom + SPACING;
|
|
|
+ // left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
|
|
|
+ // top = triggerRect.bottom + SPACING;
|
|
|
+ left = isWidthOverFlow ? containerRect.left : (pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left);
|
|
|
+ top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING : triggerRect.top + triggerRect.height + SPACING;
|
|
|
break;
|
|
|
case 'bottomRight':
|
|
|
- left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
|
|
|
- top = triggerRect.bottom + SPACING;
|
|
|
+ // left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
|
|
|
+ // top = triggerRect.bottom + SPACING;
|
|
|
+ left = isWidthOverFlow ? containerRect.right + offsetWidth : (pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right);
|
|
|
+ top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING : triggerRect.top + triggerRect.height + SPACING;
|
|
|
translateX = -1;
|
|
|
break;
|
|
|
case 'right':
|
|
|
- left = triggerRect.right + SPACING;
|
|
|
- top = middleY;
|
|
|
+ // left = triggerRect.right + SPACING;
|
|
|
+ // top = middleY;
|
|
|
+ left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
|
|
|
+ top = isHeightOverFlow ? (isTriggerNearTop ? containerRect.top + wrapperRect.height / 2 : containerRect.bottom - wrapperRect.height / 2 + offsetHeight) : middleY;
|
|
|
translateY = -0.5;
|
|
|
break;
|
|
|
case 'rightTop':
|
|
|
- left = triggerRect.right + SPACING;
|
|
|
- top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
|
|
|
+ // left = triggerRect.right + SPACING;
|
|
|
+ // top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
|
|
|
+ left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
|
|
|
+ top = isHeightOverFlow ? containerRect.top : (pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top);
|
|
|
break;
|
|
|
case 'rightBottom':
|
|
|
- left = triggerRect.right + SPACING;
|
|
|
- top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
|
|
|
+ // left = triggerRect.right + SPACING;
|
|
|
+ // top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
|
|
|
+ left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
|
|
|
+ top = isHeightOverFlow ? containerRect.bottom + offsetHeight : (pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom);
|
|
|
translateY = -1;
|
|
|
break;
|
|
|
case 'leftTopOver':
|
|
@@ -574,18 +639,18 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
|
|
|
// console.log('containerRect: ', containerRect, 'triggerRect: ', triggerRect, 'wrapperRect: ', wrapperRect);
|
|
|
|
|
|
- let style = this.calcPosStyle(triggerRect, wrapperRect, containerRect);
|
|
|
+ let style = this.calcPosStyle({ triggerRect, wrapperRect, containerRect });
|
|
|
|
|
|
let position = this.getProp('position');
|
|
|
|
|
|
if (this.getProp('autoAdjustOverflow')) {
|
|
|
// console.log('style: ', style, '\ntriggerRect: ', triggerRect, '\nwrapperRect: ', wrapperRect);
|
|
|
- const adjustedPos = this.adjustPosIfNeed(position, style, triggerRect, wrapperRect, containerRect);
|
|
|
+ const { position: adjustedPos, isHeightOverFlow, isWidthOverFlow } = this.adjustPosIfNeed(position, style, triggerRect, wrapperRect, containerRect);
|
|
|
|
|
|
- if (position !== adjustedPos) {
|
|
|
+ if (position !== adjustedPos || isHeightOverFlow || isWidthOverFlow) {
|
|
|
position = adjustedPos;
|
|
|
|
|
|
- style = this.calcPosStyle(triggerRect, wrapperRect, containerRect, position);
|
|
|
+ style = this.calcPosStyle({ triggerRect, wrapperRect, containerRect, position, spacing: null, isOverFlow: [ isHeightOverFlow, isWidthOverFlow ] });
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -598,17 +663,66 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
};
|
|
|
|
|
|
isLR(position = '') {
|
|
|
- return position.indexOf('left') === 0 || position.indexOf('right') === 0;
|
|
|
+ return position.includes('left') || position.includes('right');
|
|
|
}
|
|
|
|
|
|
isTB(position = '') {
|
|
|
- return position.indexOf('top') === 0 || position.indexOf('bottom') === 0;
|
|
|
+ return position.includes('top') || position.includes('bottom');
|
|
|
+ }
|
|
|
+
|
|
|
+ isReverse(rowSpace: number, reverseSpace: number, size: number) {
|
|
|
+ // 原空间不足,反向空间足够
|
|
|
+ // Insufficient original space, enough reverse space
|
|
|
+ return rowSpace < size && reverseSpace > size;
|
|
|
+ }
|
|
|
+
|
|
|
+ isOverFlow(rowSpace: number, reverseSpace: number, size: number){
|
|
|
+ // 原空间且反向空间都不足
|
|
|
+ // The original space and the reverse space are not enough
|
|
|
+ return rowSpace < size && reverseSpace < size;
|
|
|
+ }
|
|
|
+
|
|
|
+ isHalfOverFlow(posSpace: number, negSpace: number, size: number){
|
|
|
+ // 正半空间或者负半空间不足,即表示有遮挡,需要偏移
|
|
|
+ // Insufficient positive half space or negative half space means that there is occlusion and needs to be offset
|
|
|
+ return posSpace < size || negSpace < size;
|
|
|
+ }
|
|
|
+
|
|
|
+ isHalfAllEnough(posSpace: number, negSpace: number, size: number){
|
|
|
+ // 正半空间和负半空间都足够,即表示可以从 topLeft/topRight 变成 top
|
|
|
+ // Both positive and negative half-spaces are sufficient, which means you can change from topLeft/topRight to top
|
|
|
+ return posSpace >= size || negSpace >= size;
|
|
|
+ }
|
|
|
+
|
|
|
+ getReverse(viewOverFlow: boolean, containerOverFlow: boolean, shouldReverseView: boolean, shouldReverseContainer: boolean) {
|
|
|
+ /**
|
|
|
+ * 基于视口和容器一起判断,以下几种情况允许从原方向转到反方向,以判断是否应该由top->bottom为例子
|
|
|
+ *
|
|
|
+ * 1. 视口上下空间不足 且 容器上空间❌下空间✅
|
|
|
+ * 2. 视口上空间❌下空间✅ 且 容器上下空间不足
|
|
|
+ * 3. 视口上空间❌下空间✅ 且 容器上空间❌下空间✅
|
|
|
+ *
|
|
|
+ * Based on the judgment of the viewport and the container, the following situations are allowed to turn from the original direction to the opposite direction
|
|
|
+ * to judge whether it should be top->bottom as an example
|
|
|
+ * 1. There is insufficient space above and below the viewport and the space above the container ❌ the space below ✅
|
|
|
+ * 2. The space above the viewport ❌ the space below ✅ and the space above and below the container is insufficient
|
|
|
+ * 3. Viewport upper space ❌ lower space✅ and container upper space ❌ lower space✅
|
|
|
+ */
|
|
|
+ return (viewOverFlow && shouldReverseContainer) || (shouldReverseView && containerOverFlow) || (shouldReverseView && shouldReverseContainer);
|
|
|
}
|
|
|
|
|
|
// place the dom correctly
|
|
|
adjustPosIfNeed(position: Position | string, style: Record<string, any>, triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect) {
|
|
|
const { innerWidth, innerHeight } = window;
|
|
|
- const { spacing } = this.getProps();
|
|
|
+ const { spacing, margin } = this.getProps();
|
|
|
+
|
|
|
+ const marginLeft = typeof margin === 'number' ? margin : margin.marginLeft;
|
|
|
+ const marginTop = typeof margin === 'number' ? margin : margin.marginTop;
|
|
|
+ const marginRight = typeof margin === 'number' ? margin : margin.marginRight;
|
|
|
+ const marginBottom = typeof margin === 'number' ? margin : margin.marginBottom;
|
|
|
+
|
|
|
+ let isHeightOverFlow = false;
|
|
|
+ let isWidthOverFlow = false;
|
|
|
|
|
|
if (wrapperRect.width > 0 && wrapperRect.height > 0) {
|
|
|
// let clientLeft = left + translateX * wrapperRect.width - containerRect.scrollLeft;
|
|
@@ -638,144 +752,274 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
|
|
|
|
|
|
// The wrapperR ect.top|bottom equivalent cannot be directly used here for comparison, which is easy to cause jitter
|
|
|
|
|
|
- const shouldReverseTop = clientTop < wrapperRect.height + spacing && restClientBottom > wrapperRect.height + spacing;
|
|
|
- const shouldReverseLeft = clientLeft < wrapperRect.width + spacing && restClientRight > wrapperRect.width + spacing;
|
|
|
- const shouldReverseBottom = restClientBottom < wrapperRect.height + spacing && clientTop > wrapperRect.height + spacing;
|
|
|
- const shouldReverseRight = restClientRight < wrapperRect.width + spacing && clientLeft > wrapperRect.width + spacing;
|
|
|
+ // 基于视口的微调判断
|
|
|
+ // Fine-tuning judgment based on viewport
|
|
|
+ const shouldViewReverseTop = clientTop - marginTop < wrapperRect.height + spacing && restClientBottom - marginBottom > wrapperRect.height + spacing;
|
|
|
+ const shouldViewReverseLeft = clientLeft - marginLeft < wrapperRect.width + spacing && restClientRight - marginRight > wrapperRect.width + spacing;
|
|
|
+ const shouldViewReverseBottom = restClientBottom - marginBottom < wrapperRect.height + spacing && clientTop - marginTop > wrapperRect.height + spacing;
|
|
|
+ const shouldViewReverseRight = restClientRight - marginRight < wrapperRect.width + spacing && clientLeft - marginLeft > wrapperRect.width + spacing;
|
|
|
+ const shouldViewReverseTopOver = restClientTop - marginBottom< wrapperRect.height + spacing && clientBottom - marginTop> wrapperRect.height + spacing;
|
|
|
+ const shouldViewReverseBottomOver = clientBottom - marginTop < wrapperRect.height + spacing && restClientTop - marginBottom > wrapperRect.height + spacing;
|
|
|
+
|
|
|
+ const shouldViewReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
|
|
|
+ const shouldViewReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;
|
|
|
+ const shouldViewReverseLeftSide = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
|
|
|
+ const shouldViewReverseRightSide = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
|
|
|
+
|
|
|
const shouldReverseTopOver = restClientTop < wrapperRect.height + spacing && clientBottom > wrapperRect.height + spacing;
|
|
|
const shouldReverseBottomOver = clientBottom < wrapperRect.height + spacing && restClientTop > wrapperRect.height + spacing;
|
|
|
|
|
|
- const shouldReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
|
|
|
- const shouldReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;
|
|
|
- const shouldReverseLeftSide = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
|
|
|
- const shouldReverseRightSide = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
|
|
|
-
|
|
|
const shouldReverseLeftOver = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
|
|
|
const shouldReverseRightOver = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
|
|
|
|
|
|
+ // 基于容器的微调判断
|
|
|
+ // Fine-tuning judgment based on container
|
|
|
+ const clientTopInContainer = clientTop - containerRect.top;
|
|
|
+ const clientLeftInContainer = clientLeft - containerRect.left;
|
|
|
+ const clientBottomInContainer = clientTopInContainer + triggerRect.height;
|
|
|
+ const clientRightInContainer = clientLeftInContainer + triggerRect.width;
|
|
|
+
|
|
|
+ const restClientBottomInContainer = containerRect.bottom - clientBottom;
|
|
|
+ const restClientRightInContainer = containerRect.right - clientRight;
|
|
|
+ const restClientTopInContainer = restClientBottomInContainer + triggerRect.height;
|
|
|
+ const restClientLeftInContainer = restClientRightInContainer + triggerRect.width;
|
|
|
+
|
|
|
+ // 当原空间不足,反向空间足够时,可以反向。
|
|
|
+ // When the original space is insufficient and the reverse space is sufficient, the reverse can be performed.
|
|
|
+ const shouldContainerReverseTop = this.isReverse(clientTopInContainer - marginTop, restClientBottomInContainer - marginBottom, wrapperRect.height + spacing);
|
|
|
+ const shouldContainerReverseLeft = this.isReverse(clientLeftInContainer - marginLeft, restClientRightInContainer - marginRight, wrapperRect.width + spacing);
|
|
|
+ const shouldContainerReverseBottom = this.isReverse(restClientBottomInContainer - marginBottom, clientTopInContainer - marginTop, wrapperRect.height + spacing);
|
|
|
+ const shouldContainerReverseRight = this.isReverse(restClientRightInContainer - marginRight, clientLeftInContainer - marginLeft, wrapperRect.width + spacing);
|
|
|
+ const shouldContainerReverseTopOver = this.isReverse(restClientTopInContainer - marginBottom, clientBottomInContainer - marginTop, wrapperRect.height + spacing);
|
|
|
+ const shouldContainerReverseBottomOver = this.isReverse(clientBottomInContainer - marginTop, restClientTopInContainer - marginBottom, wrapperRect.height + spacing);
|
|
|
+
|
|
|
+ const shouldContainerReverseTopSide = this.isReverse(restClientTopInContainer, clientBottomInContainer, wrapperRect.height);
|
|
|
+ const shouldContainerReverseBottomSide = this.isReverse(clientBottomInContainer, restClientTopInContainer, wrapperRect.height);
|
|
|
+ const shouldContainerReverseLeftSide = this.isReverse(restClientLeftInContainer, clientRightInContainer, wrapperRect.width);
|
|
|
+ const shouldContainerReverseRightSide = this.isReverse(clientRightInContainer, restClientLeftInContainer, wrapperRect.width);
|
|
|
+
|
|
|
+ const halfHeight = triggerRect.height / 2;
|
|
|
+ const halfWidth = triggerRect.width / 2;
|
|
|
+
|
|
|
+ // 视口, 原空间与反向空间是否都不足判断
|
|
|
+ // Viewport, whether the original space and the reverse space are insufficient to judge
|
|
|
+ const isViewYOverFlow = this.isOverFlow(clientTop - marginTop, restClientBottom - marginBottom, wrapperRect.height + spacing);
|
|
|
+ const isViewXOverFlow = this.isOverFlow(clientLeft - marginLeft, restClientRight - marginRight, wrapperRect.width + spacing);
|
|
|
+ const isViewYOverFlowSide = this.isOverFlow(clientBottom - marginTop, restClientTop - marginBottom, wrapperRect.height + spacing);
|
|
|
+ const isViewXOverFlowSide = this.isOverFlow(clientRight - marginLeft, restClientLeft - marginRight, wrapperRect.width + spacing);
|
|
|
+ const isViewYOverFlowSideHalf = this.isHalfOverFlow(clientBottom - halfHeight, restClientTop - halfHeight, wrapperRect.height / 2);
|
|
|
+ const isViewXOverFlowSideHalf = this.isHalfOverFlow(clientRight - halfWidth, restClientLeft - halfWidth, wrapperRect.width / 2);
|
|
|
+ const isViewYEnoughSideHalf = this.isHalfAllEnough(clientBottom - halfHeight, restClientTop - halfHeight, wrapperRect.height / 2);
|
|
|
+ const isViewXEnoughSideHalf = this.isHalfAllEnough(clientRight - halfWidth, restClientLeft - halfWidth, wrapperRect.width / 2);
|
|
|
+
|
|
|
+ // 容器, 原空间与反向空间是否都不足判断
|
|
|
+ // container, whether the original space and the reverse space are insufficient to judge
|
|
|
+ const isContainerYOverFlow = this.isOverFlow(clientTopInContainer - marginTop, restClientBottomInContainer - marginBottom, wrapperRect.height + spacing);
|
|
|
+ const isContainerXOverFlow = this.isOverFlow(clientLeftInContainer - marginLeft, restClientRightInContainer - marginRight, wrapperRect.width + spacing);
|
|
|
+ const isContainerYOverFlowSide = this.isOverFlow(clientBottomInContainer - marginTop, restClientTopInContainer - marginBottom, wrapperRect.height + spacing);
|
|
|
+ const isContainerXOverFlowSide = this.isOverFlow(clientRightInContainer - marginLeft, restClientLeftInContainer - marginRight, wrapperRect.width + spacing);
|
|
|
+ const isContainerYOverFlowSideHalf = this.isHalfOverFlow(clientBottomInContainer - halfHeight, restClientTopInContainer - halfHeight, wrapperRect.height / 2);
|
|
|
+ const isContainerXOverFlowSideHalf = this.isHalfOverFlow(clientRightInContainer - halfWidth, restClientLeftInContainer - halfWidth, wrapperRect.width / 2);
|
|
|
+ const isContainerYEnoughSideHalf = this.isHalfAllEnough(clientBottomInContainer - halfHeight, restClientTopInContainer - halfHeight, wrapperRect.height / 2);
|
|
|
+ const isContainerXEnoughSideHalf = this.isHalfAllEnough(clientRightInContainer - halfWidth, restClientLeftInContainer - halfWidth, wrapperRect.width / 2);
|
|
|
+
|
|
|
+ // 综合 viewport + container 判断微调,即视口 + 容器都放置不行时才能考虑位置调整
|
|
|
+ // Comprehensive viewport + container judgment fine-tuning, that is, the position adjustment can only be considered when the viewport + container cannot be placed.
|
|
|
+ const shouldReverseTop = this.getReverse(isViewYOverFlow, isContainerYOverFlow, shouldViewReverseTop, shouldContainerReverseTop);
|
|
|
+ const shouldReverseLeft = this.getReverse(isViewXOverFlow, isContainerXOverFlow, shouldViewReverseLeft, shouldContainerReverseLeft);
|
|
|
+ const shouldReverseBottom = this.getReverse(isViewYOverFlow, isContainerYOverFlow, shouldViewReverseBottom, shouldContainerReverseBottom);
|
|
|
+ const shouldReverseRight = this.getReverse(isViewXOverFlow, isContainerXOverFlow, shouldViewReverseRight, shouldContainerReverseRight);
|
|
|
+
|
|
|
+ // const shouldReverseTopOver = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseTopOver, shouldContainerReverseTopOver);
|
|
|
+ // const shouldReverseBottomOver = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseBottomOver, shouldContainerReverseBottomOver);
|
|
|
+
|
|
|
+ const shouldReverseTopSide = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseTopSide, shouldContainerReverseTopSide);
|
|
|
+ const shouldReverseBottomSide = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseBottomSide, shouldContainerReverseBottomSide);
|
|
|
+ const shouldReverseLeftSide = this.getReverse(isViewXOverFlowSide, isContainerXOverFlowSide, shouldViewReverseLeftSide, shouldContainerReverseLeftSide);
|
|
|
+ const shouldReverseRightSide = this.getReverse(isViewXOverFlowSide, isContainerXOverFlowSide, shouldViewReverseRightSide, shouldContainerReverseRightSide);
|
|
|
+
|
|
|
+ const isYOverFlowSideHalf = isViewYOverFlowSideHalf && isContainerYOverFlowSideHalf;
|
|
|
+ const isXOverFlowSideHalf = isViewXOverFlowSideHalf && isContainerXOverFlowSideHalf;
|
|
|
+
|
|
|
switch (position) {
|
|
|
case 'top':
|
|
|
if (shouldReverseTop) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
+ }
|
|
|
+ if (isXOverFlowSideHalf && (shouldReverseLeftSide || shouldReverseRightSide)) {
|
|
|
+ position = this._adjustPos(position, true, 'expand', shouldReverseLeftSide ? 'Right' : 'Left');
|
|
|
}
|
|
|
break;
|
|
|
case 'topLeft':
|
|
|
if (shouldReverseTop) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseLeftSide && widthIsBigger) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
+ }
|
|
|
+ if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, true, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'topRight':
|
|
|
if (shouldReverseTop) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseRightSide && widthIsBigger) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
+ }
|
|
|
+ if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, true, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'left':
|
|
|
if (shouldReverseLeft) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
+ }
|
|
|
+ if (isYOverFlowSideHalf && (shouldReverseTopSide || shouldReverseBottomSide)) {
|
|
|
+ position = this._adjustPos(position, false, 'expand', shouldReverseTopSide ? 'Bottom' : 'Top');
|
|
|
}
|
|
|
break;
|
|
|
case 'leftTop':
|
|
|
if (shouldReverseLeft) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
if (shouldReverseTopSide && heightIsBigger) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
+ }
|
|
|
+ if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, false, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'leftBottom':
|
|
|
if (shouldReverseLeft) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
if (shouldReverseBottomSide && heightIsBigger) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
+ }
|
|
|
+ if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, false, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'bottom':
|
|
|
if (shouldReverseBottom) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
+ }
|
|
|
+ if (isXOverFlowSideHalf && (shouldReverseLeftSide || shouldReverseRightSide)) {
|
|
|
+ position = this._adjustPos(position, true, 'expand', shouldReverseLeftSide ? 'Right' : 'Left');
|
|
|
}
|
|
|
break;
|
|
|
case 'bottomLeft':
|
|
|
if (shouldReverseBottom) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseLeftSide && widthIsBigger) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
+ }
|
|
|
+ if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, true, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'bottomRight':
|
|
|
if (shouldReverseBottom) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseRightSide && widthIsBigger) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
+ }
|
|
|
+ if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, true, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'right':
|
|
|
if (shouldReverseRight) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
+ }
|
|
|
+ if (isYOverFlowSideHalf && (shouldReverseTopSide || shouldReverseBottomSide)) {
|
|
|
+ position = this._adjustPos(position, false, 'expand', shouldReverseTopSide ? 'Bottom' : 'Top');
|
|
|
}
|
|
|
break;
|
|
|
case 'rightTop':
|
|
|
if (shouldReverseRight) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
if (shouldReverseTopSide && heightIsBigger) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
+ }
|
|
|
+ if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, false, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'rightBottom':
|
|
|
if (shouldReverseRight) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
if (shouldReverseBottomSide && heightIsBigger) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
+ }
|
|
|
+ if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
|
|
|
+ position = this._adjustPos(position, false, 'reduce');
|
|
|
}
|
|
|
break;
|
|
|
case 'leftTopOver':
|
|
|
if (shouldReverseTopOver) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseLeftOver) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
break;
|
|
|
case 'leftBottomOver':
|
|
|
if (shouldReverseBottomOver) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseLeftOver) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
break;
|
|
|
case 'rightTopOver':
|
|
|
if (shouldReverseTopOver) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseRightOver) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
break;
|
|
|
case 'rightBottomOver':
|
|
|
if (shouldReverseBottomOver) {
|
|
|
- position = this._reversePos(position, true);
|
|
|
+ position = this._adjustPos(position, true);
|
|
|
}
|
|
|
if (shouldReverseRightOver) {
|
|
|
- position = this._reversePos(position);
|
|
|
+ position = this._adjustPos(position);
|
|
|
}
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
+
|
|
|
+ // 判断溢出 Judgment overflow
|
|
|
+ // 上下方向 top and bottom
|
|
|
+ if (this.isTB(position)){
|
|
|
+ isHeightOverFlow = isViewYOverFlow && isContainerYOverFlow;
|
|
|
+ if (position === 'top' || position === 'bottom') {
|
|
|
+ isWidthOverFlow = isViewXOverFlowSideHalf && isContainerXOverFlowSideHalf;
|
|
|
+ } else {
|
|
|
+ isWidthOverFlow = isViewXOverFlowSide && isContainerXOverFlowSide;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 左右方向 left and right
|
|
|
+ if (this.isLR(position)){
|
|
|
+ isWidthOverFlow = isViewXOverFlow && isContainerXOverFlow;
|
|
|
+ if (position === 'left' || position === 'right') {
|
|
|
+ isHeightOverFlow = isViewYOverFlowSideHalf && isContainerYOverFlowSideHalf;
|
|
|
+ } else {
|
|
|
+ isHeightOverFlow = isViewYOverFlowSide && isContainerYOverFlowSide;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- return position;
|
|
|
+ return { position, isHeightOverFlow, isWidthOverFlow };
|
|
|
}
|
|
|
|
|
|
delayHide = () => {
|