Skip to content

Commit

Permalink
feat(components/popover): 新增 widthByTrigger 窗体宽度与触发元素一致
Browse files Browse the repository at this point in the history
  • Loading branch information
mengxinssfd committed Aug 25, 2023
1 parent 9b99f1c commit b2f9033
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 23 deletions.
2 changes: 2 additions & 0 deletions packages/components/src/popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const Popover: React.FC<PopoverProps> = React.forwardRef<
delay,
leaveDelay,
onVisibleChange,
widthByTrigger,
attrs = {},
} = props as PopoverRequiredPartProps;
const rootName = getComponentClass(name);
Expand All @@ -74,6 +75,7 @@ export const Popover: React.FC<PopoverProps> = React.forwardRef<
childrenRef,
balloonRef,
{
widthByTrigger,
placement,
viewport,
appendTo,
Expand Down
16 changes: 16 additions & 0 deletions packages/components/src/popover/demo/width-by-trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* title: 使用触发元素的宽度
*/

import React from 'react';
import { Popover, Button } from '@tool-pack/react-ui';

const App: React.FC = () => {
return (
<Popover widthByTrigger trigger="click" content="宽度与按钮一致">
<Button>click me~~</Button>
</Popover>
);
};

export default App;
2 changes: 2 additions & 0 deletions packages/components/src/popover/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Popover 气泡弹框。
<code src="./demo/contextmenu.tsx"></code>
<code src="./demo/delay.tsx"></code>
<code src="./demo/on-visible-change.tsx"></code>
<code src="./demo/width-by-trigger.tsx"></code>

## API

Expand All @@ -56,4 +57,5 @@ Popover 的属性说明如下:
| childrenRef | 用于获取 Popover 包裹的 ref viewport | () => HTMLElement | -- | -- |
| delay | trigger 为 hover 的开启延时 | number | 0 (ms) | -- |
| leaveDelay | trigger 为 hover 的关闭延时 | number | 200 (ms) | -- |
| widthByTrigger | 窗体宽度与触发元素保持一致 | boolean | -- | -- |
| attrs | 组件 html 根元素的所有属性 | Partial\<React.HTMLAttributes\<HTMLDivElement>> | -- | -- |
52 changes: 37 additions & 15 deletions packages/components/src/popover/popover.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,50 +47,72 @@ function replaceBalloonClass(balloon: HTMLElement, placement: Placement_12) {
}

export function usePosition(
refEl: React.RefObject<HTMLElement>,
relEl: React.MutableRefObject<HTMLElement | undefined>,
triggerElRef: React.RefObject<HTMLElement>,
balloonElRef: React.MutableRefObject<HTMLElement | undefined>,
{
offset,
placement,
viewport: getViewportEl,
appendTo,
widthByTrigger,
}: Pick<
PopoverRequiredPartProps,
'offset' | 'placement' | 'viewport' | 'appendTo'
'offset' | 'placement' | 'viewport' | 'appendTo' | 'widthByTrigger'
>,
) {
const _placement = useRef(placement);
// const forceUpdate = useForceUpdate();

const refreshPosition = useMemo(() => {
const refreshPosition = (): void => {
const ref = refEl.current;
const balloon = relEl.current;
if (!ref || !balloon || balloon.style.display === 'none') return;
const triggerEl = triggerElRef.current;
const balloonEl = balloonElRef.current;
if (!triggerEl || !balloonEl || balloonEl.style.display === 'none')
return;

const viewportEl = appendTo?.() || getViewportEl?.() || ref;
const viewportEl = appendTo?.() || getViewportEl?.() || triggerEl;
const containerEl =
appendTo?.() || (ref.offsetParent as HTMLElement) || ref;
appendTo?.() || (triggerEl.offsetParent as HTMLElement) || triggerEl;

// 窗体宽度跟触发元素一致
if (widthByTrigger) {
balloonEl.style.width = triggerEl.offsetWidth + 'px';
void balloonEl.offsetWidth;
}

const lastPlace = _placement.current;
const place = calcPlacement(ref, balloon, lastPlace, viewportEl, offset);
const place = calcPlacement(
triggerEl,
balloonEl,
lastPlace,
viewportEl,
offset,
);

const distance: [number, number] =
appendTo === null ? [0, 0] : calcDistanceWithParent(ref, containerEl);
const { x, y } = calcPosition(ref, balloon, place, offset, distance);
balloon.style.top = y + 'px';
balloon.style.left = x + 'px';
appendTo === null
? [0, 0]
: calcDistanceWithParent(triggerEl, containerEl);
const { x, y } = calcPosition(
triggerEl,
balloonEl,
place,
offset,
distance,
);
balloonEl.style.top = y + 'px';
balloonEl.style.left = x + 'px';

if (placement !== place || lastPlace !== place)
replaceBalloonClass(balloon, place);
replaceBalloonClass(balloonEl, place);

_placement.current = place;
};
return throttle(() => refreshPosition(), 5, {
leading: true,
trailing: true,
});
}, [offset, placement, refEl, relEl]);
}, [offset, placement, triggerElRef, balloonElRef, widthByTrigger]);

return [
refreshPosition,
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/popover/popover.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface PopoverProps
appendTo?: null | (() => HTMLElement | null);
trigger?: PopoverTrigger[] | PopoverTrigger;
visible?: boolean;
widthByTrigger?: boolean;
disabled?: boolean;
offset?: number;
destroyOnHide?: boolean;
Expand Down
16 changes: 8 additions & 8 deletions packages/components/src/popover/popover.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { calcDistanceWithParent } from '@tool-pack/dom';
* 计算上下左右4个方位是否可以放得下窗体
*/
export function calcPlacement(
refEl: HTMLElement,
relEl: HTMLElement,
triggerEl: HTMLElement,
balloonEl: HTMLElement,
placement: Placement_12 = 'top',
containerEl = globalThis.document?.body,
offset = 10,
Expand All @@ -30,9 +30,9 @@ export function calcPlacement(
return { width: containerEl.clientWidth, height: containerEl.clientHeight };
};

const sizes = { ref: getSize(refEl), rel: getSize(relEl) };
const sizes = { ref: getSize(triggerEl), rel: getSize(balloonEl) };
const viewportSize = getViewportSize();
const [distanceT, distanceL] = calcDistanceWithParent(refEl, containerEl);
const [distanceT, distanceL] = calcDistanceWithParent(triggerEl, containerEl);
const [scrollTop, scrollLeft] = getScrollPoint();

const map: Record<Placement, () => Placement> = {
Expand Down Expand Up @@ -86,14 +86,14 @@ function getSize(el: HTMLElement): Size {
}

export function calcPosition(
refEl: HTMLElement,
relEl: HTMLElement,
triggerEl: HTMLElement,
balloonEl: HTMLElement,
placement: Placement_12 = 'top',
offset = 10,
[distanceT, distanceL]: [number, number] = [0, 0],
): { x: number; y: number } {
const ref = getSize(refEl);
const rel = getSize(relEl);
const ref = getSize(triggerEl);
const rel = getSize(balloonEl);

const commonFn = {
vtcX: () => distanceL + (ref.width - rel.width) / 2,
Expand Down

0 comments on commit b2f9033

Please sign in to comment.