feat(FloatingPanel): add draggable prop (#13773)

Co-authored-by: Junwei Wang <junwei.wang@algento.com>
This commit is contained in:
Gavin
2026-02-18 10:13:16 +04:00
committed by GitHub
parent b1283de219
commit 230ed26763
7 changed files with 124 additions and 0 deletions
@@ -29,6 +29,7 @@ export const floatingPanelProps = {
anchors: makeArrayProp<number>(),
duration: makeNumericProp(0.3),
magnetic: truthProp,
draggable: truthProp,
contentDraggable: truthProp,
lockScroll: Boolean,
safeAreaInsetBottom: truthProp,
@@ -97,6 +98,8 @@ export default defineComponent({
const touch = useTouch();
const onTouchstart = (e: TouchEvent) => {
if (!props.draggable) return;
touch.start(e);
dragging.value = true;
startY = -height.value;
@@ -104,6 +107,8 @@ export default defineComponent({
};
const onTouchmove = (e: TouchEvent) => {
if (!props.draggable) return;
touch.move(e);
const target = e.target as Element;
@@ -130,8 +135,17 @@ export default defineComponent({
const onTouchend = () => {
maxScroll = -1;
if (!dragging.value) {
return;
}
dragging.value = false;
if (!props.draggable) {
return;
}
if (props.magnetic) {
height.value = closest(anchors.value, height.value);
} else {
@@ -162,6 +176,10 @@ export default defineComponent({
return slots.header();
}
if (!props.draggable) {
return null;
}
return (
<div class={bem('header')}>
<div class={bem('header-bar')} />
@@ -93,6 +93,18 @@ When `magnetic` is set to `false`, the panel will not automatically snap to anch
</van-floating-panel>
```
### Disable Dragging
You can disable the dragging functionality of the panel through the `draggable` attribute. When set to `false`, the panel will not be draggable, and the header drag bar will be hidden.
```html
<van-floating-panel :draggable="false">
<div style="text-align: center; padding: 15px">
<p>This panel cannot be dragged</p>
</div>
</van-floating-panel>
```
## API
### Props
@@ -104,6 +116,7 @@ When `magnetic` is set to `false`, the panel will not automatically snap to anch
| duration | Transition duration, unit second | _number \| string_ | `0.3` |
| magnetic | Whether to enable magnetic adsorption to anchors. When disabled, panel can stop at any position within the anchor boundaries | _boolean_ | `true` |
| content-draggable | Allow dragging content | _boolean_ | `true` |
| draggable | Whether to allow dragging the panel. When disabled, the header drag bar will be hidden | _boolean_ | `true` |
| lock-scroll `v4.6.4` | When not dragging, Whether to lock background scroll | _boolean_ | `false` |
| safe-area-inset-bottom | Whether to enable bottom safe area adaptation | _boolean_ | `true` |
@@ -93,6 +93,18 @@ export default {
</van-floating-panel>
```
### 禁用拖拽
你可以通过 `draggable` 属性来禁用面板的拖拽功能。当设置为 `false` 时,面板将不可拖拽,同时头部拖拽栏也会被隐藏。
```html
<van-floating-panel :draggable="false">
<div style="text-align: center; padding: 15px">
<p>该面板不可拖拽</p>
</div>
</van-floating-panel>
```
## API
### Props
@@ -104,6 +116,7 @@ export default {
| duration | 动画时长,单位秒,设置为 0 可以禁用动画 | _number \| string_ | `0.3` |
| magnetic | 是否启用磁力吸附到锚点。禁用后面板可在锚点边界范围内任意位置停留 | _boolean_ | `true` |
| content-draggable | 允许拖拽内容容器 | _boolean_ | `true` |
| draggable | 是否允许拖拽面板。禁用后头部拖拽栏会被隐藏 | _boolean_ | `true` |
| lock-scroll `v4.6.4` | 当不拖拽时,是否锁定背景滚动 | _boolean_ | `false` |
| safe-area-inset-bottom | 是否开启[底部安全区适配](#/zh-CN/advanced-usage#di-bu-an-quan-qu-gua-pei) | _boolean_ | `true` |
@@ -15,6 +15,8 @@ const t = useTranslate({
customAnchors: '自定义锚点',
headDragOnly: '仅头部拖拽',
disableMagnetic: '禁用吸附',
disableDragging: '禁用拖拽',
panelUnDrag: '该面板不可拖拽',
panelShowHeight: '面板显示高度',
contentUnDrag: '内容不可拖拽',
magneticDisabled: '已禁用磁力吸附,可在边界内任意停留',
@@ -23,6 +25,8 @@ const t = useTranslate({
customAnchors: 'Custom Anchors',
headDragOnly: 'Head Drag Only',
disableMagnetic: 'Disable Magnetic',
disableDragging: 'Disable Dragging',
panelUnDrag: 'This panel cannot be dragged',
panelShowHeight: 'Panel Show Height',
contentUnDrag: 'Content cannot be dragged',
magneticDisabled: 'Magnetic disabled, free positioning within boundaries',
@@ -76,5 +80,13 @@ const height = ref(anchors[0]);
</div>
</van-floating-panel>
</van-tab>
<van-tab :title="t('disableDragging')">
<van-floating-panel :draggable="false">
<div style="text-align: center; padding: 15px">
<p>{{ t('panelUnDrag') }}</p>
</div>
</van-floating-panel>
</van-tab>
</van-tabs>
</template>
@@ -56,6 +56,16 @@ exports[`should render demo and match snapshot 1`] = `
style="display:none;"
>
</div>
<div
id="van-tab"
role="tabpanel"
class="van-tab__panel"
tabindex="-1"
aria-labelledby="van-tabs-4"
data-allow-mismatch="attribute"
style="display:none;"
>
</div>
</div>
</div>
`;
@@ -60,6 +60,19 @@ exports[`should render demo and match snapshot 1`] = `
Disable Magnetic
</span>
</div>
<div
id="van-tabs-4"
role="tab"
class="van-tab van-tab--line van-tab--grow"
tabindex="-1"
aria-selected="false"
aria-controls="van-tab"
data-allow-mismatch="attribute"
>
<span class="van-tab__text">
Disable Dragging
</span>
</div>
<div
class="van-tabs__line"
style="transform: translateX(50px) translateX(-50%);"
@@ -306,6 +319,16 @@ exports[`should render demo and match snapshot 1`] = `
style="display: none;"
>
</div>
<div
id="van-tab"
role="tabpanel"
class="van-tab__panel"
tabindex="-1"
aria-labelledby="van-tabs-4"
data-allow-mismatch="attribute"
style="display: none;"
>
</div>
</div>
</div>
`;
@@ -189,3 +189,38 @@ test('should add padding bottom to content when panel is not fully expanded', as
await wrapper.setProps({ height: 400 });
expect(content.style.paddingBottom).toBe('0px');
});
test('should not allow dragging when draggable is false', async () => {
const wrapper = mount({
render() {
return (
<FloatingPanel
anchors={[100, 200, 400]}
draggable={false}
onHeightChange={(h) => this.$emit('change', h)}
>
Content
</FloatingPanel>
);
},
});
expect(wrapper.find('.van-floating-panel__header').exists()).toBe(false);
await triggerDrag(wrapper.find('.van-floating-panel__content'), 0, -199);
await later();
expect(wrapper.emitted('change')).toBeFalsy();
});
test('should render header slot even when draggable is false', () => {
const wrapper = mount(FloatingPanel, {
props: {
draggable: false,
},
slots: {
header: () => 'Custom Header',
},
});
expect(wrapper.html()).toContain('Custom Header');
});