feat(FloatingBubble): allow gap xy to be set separately (#13297) (#13429)

Co-authored-by: neverland <jait.chen@foxmail.com>
This commit is contained in:
Wei-Liang Liou
2025-04-13 17:20:59 +08:00
committed by GitHub
co-authored by neverland
parent 5a7042c182
commit d739f678b4
5 changed files with 55 additions and 11 deletions
@@ -20,7 +20,7 @@ import {
addUnit, addUnit,
closest, closest,
createNamespace, createNamespace,
makeNumberProp, isObject,
makeStringProp, makeStringProp,
windowWidth, windowWidth,
windowHeight, windowHeight,
@@ -38,11 +38,15 @@ import {
FloatingBubbleAxis, FloatingBubbleAxis,
FloatingBubbleMagnetic, FloatingBubbleMagnetic,
FloatingBubbleOffset, FloatingBubbleOffset,
FloatingBubbleGap,
FloatingBubbleBoundary, FloatingBubbleBoundary,
} from './types'; } from './types';
export const floatingBubbleProps = { export const floatingBubbleProps = {
gap: makeNumberProp(24), gap: {
type: [Number, Object] as unknown as PropType<FloatingBubbleGap>,
default: 24,
},
icon: String, icon: String,
axis: makeStringProp<FloatingBubbleAxis>('y'), axis: makeStringProp<FloatingBubbleAxis>('y'),
magnetic: String as PropType<FloatingBubbleMagnetic>, magnetic: String as PropType<FloatingBubbleMagnetic>,
@@ -79,11 +83,17 @@ export default defineComponent({
height: 0, height: 0,
}); });
const gapX = computed(() =>
isObject(props.gap) ? props.gap.x : props.gap,
);
const gapY = computed(() =>
isObject(props.gap) ? props.gap.y : props.gap,
);
const boundary = computed<FloatingBubbleBoundary>(() => ({ const boundary = computed<FloatingBubbleBoundary>(() => ({
top: props.gap, top: gapY.value,
right: windowWidth.value - state.value.width - props.gap, right: windowWidth.value - state.value.width - gapX.value,
bottom: windowHeight.value - state.value.height - props.gap, bottom: windowHeight.value - state.value.height - gapY.value,
left: props.gap, left: gapX.value,
})); }));
const dragging = ref(false); const dragging = ref(false);
@@ -110,8 +120,8 @@ export default defineComponent({
const { width, height } = useRect(rootRef.value!); const { width, height } = useRect(rootRef.value!);
const { offset } = props; const { offset } = props;
state.value = { state.value = {
x: offset.x > -1 ? offset.x : windowWidth.value - width - props.gap, x: offset.x > -1 ? offset.x : windowWidth.value - width - gapX.value,
y: offset.y > -1 ? offset.y : windowHeight.value - height - props.gap, y: offset.y > -1 ? offset.y : windowHeight.value - height - gapY.value,
width, width,
height, height,
}; };
@@ -203,7 +213,7 @@ export default defineComponent({
}); });
watch( watch(
[windowWidth, windowHeight, () => props.gap, () => props.offset], [windowWidth, windowHeight, gapX, gapY, () => props.offset],
updateState, updateState,
{ deep: true }, { deep: true },
); );
+1 -1
View File
@@ -94,7 +94,7 @@ export default {
| axis | Drag direction, `xy` stands for free drag, `lock` stands for disable drag | _'x' \| 'y' \| 'xy' \| 'lock'_ | `y` | | axis | Drag direction, `xy` stands for free drag, `lock` stands for disable drag | _'x' \| 'y' \| 'xy' \| 'lock'_ | `y` |
| magnetic | Direction of automatic magnetic absorption | _'x' \| 'y'_ | - | | magnetic | Direction of automatic magnetic absorption | _'x' \| 'y'_ | - |
| icon | Bubble icon | _string_ | - | | icon | Bubble icon | _string_ | - |
| gap | Minimum gap between the bubble and the window, unit `px` | _number_ | `24` | | gap | Minimum gap between the bubble and the window, unit `px` | _number \| { x: number, y: number }_ | `24` |
| teleport | Specifies a target element where BackTop will be mounted | _string \| Element_ | `body` | | teleport | Specifies a target element where BackTop will be mounted | _string \| Element_ | `body` |
### Events ### Events
@@ -94,7 +94,7 @@ export default {
| axis | 拖拽的方向,`xy` 代表自由拖拽,`lock` 代表禁止拖拽 | _'x' \| 'y' \| 'xy' \| 'lock'_ | `y` | | axis | 拖拽的方向,`xy` 代表自由拖拽,`lock` 代表禁止拖拽 | _'x' \| 'y' \| 'xy' \| 'lock'_ | `y` |
| magnetic | 自动磁吸的方向 | _'x' \| 'y'_ | - | | magnetic | 自动磁吸的方向 | _'x' \| 'y'_ | - |
| icon | 气泡图标名称或图片链接,等同于 Icon 组件的 [name 属性](#/zh-CN/icon#props) | _string_ | - | | icon | 气泡图标名称或图片链接,等同于 Icon 组件的 [name 属性](#/zh-CN/icon#props) | _string_ | - |
| gap | 气泡与窗口的最小间距,单位为 `px` | _number_ | `24` | | gap | 气泡与窗口的最小间距,单位为 `px` | _number \| { x: number, y: number }_ | `24` |
| teleport | 指定挂载的节点,等同于 Teleport 组件的 [to 属性](https://cn.vuejs.org/api/built-in-components.html#teleport) | _string \| Element_ | `body` | | teleport | 指定挂载的节点,等同于 Teleport 组件的 [to 属性](https://cn.vuejs.org/api/built-in-components.html#teleport) | _string \| Element_ | `body` |
### Events ### Events
@@ -62,6 +62,33 @@ test('should render correctly when all props set', async () => {
restore(); restore();
}); });
test('should render with xy gaps', async () => {
useWindowSize();
const restore = mockGetBoundingClientRect({ width: 48, height: 48 });
const root = document.createElement('div');
mount(FloatingBubble, {
props: {
teleport: root,
gap: { x: 50, y: 27 },
},
});
const floatingBubbleEl = root.querySelector<HTMLDivElement>(
'.van-floating-bubble',
)!;
await later();
expect(floatingBubbleEl.style.transform).toEqual(
`translate3d(${window.innerWidth - 48 - 50}px, ${
window.innerHeight - 48 - 27
}px, 0)`,
);
restore();
});
test('should only y axis direction move when axis is default', async () => { test('should only y axis direction move when axis is default', async () => {
const restore = mockGetBoundingClientRect({ width: 48, height: 48 }); const restore = mockGetBoundingClientRect({ width: 48, height: 48 });
@@ -16,6 +16,13 @@ export type FloatingBubbleOffset = {
y: number; y: number;
}; };
export type FloatingBubbleGap =
| number
| {
x: number;
y: number;
};
export type FloatingBubbleBoundary = { export type FloatingBubbleBoundary = {
top: number; top: number;
right: number; right: number;