feat(FloatingPanel): add prop magnetic (#13608)
This commit is contained in:
@@ -28,6 +28,7 @@ export const floatingPanelProps = {
|
|||||||
height: makeNumericProp(0),
|
height: makeNumericProp(0),
|
||||||
anchors: makeArrayProp<number>(),
|
anchors: makeArrayProp<number>(),
|
||||||
duration: makeNumericProp(0.3),
|
duration: makeNumericProp(0.3),
|
||||||
|
magnetic: truthProp,
|
||||||
contentDraggable: truthProp,
|
contentDraggable: truthProp,
|
||||||
lockScroll: Boolean,
|
lockScroll: Boolean,
|
||||||
safeAreaInsetBottom: truthProp,
|
safeAreaInsetBottom: truthProp,
|
||||||
@@ -130,7 +131,13 @@ export default defineComponent({
|
|||||||
const onTouchend = () => {
|
const onTouchend = () => {
|
||||||
maxScroll = -1;
|
maxScroll = -1;
|
||||||
dragging.value = false;
|
dragging.value = false;
|
||||||
height.value = closest(anchors.value, height.value);
|
|
||||||
|
if (props.magnetic) {
|
||||||
|
height.value = closest(anchors.value, height.value);
|
||||||
|
} else {
|
||||||
|
const { min, max } = boundary.value;
|
||||||
|
height.value = Math.max(min, Math.min(max, height.value));
|
||||||
|
}
|
||||||
|
|
||||||
if (height.value !== -startY) {
|
if (height.value !== -startY) {
|
||||||
emit('heightChange', { height: height.value });
|
emit('heightChange', { height: height.value });
|
||||||
|
|||||||
@@ -78,6 +78,21 @@ By default, both the header and content areas of FloatingPanel can be dragged, b
|
|||||||
</van-floating-panel>
|
</van-floating-panel>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Disable Magnetic Adsorption
|
||||||
|
|
||||||
|
By default, when dragging ends, the panel will automatically snap to the nearest anchor point. You can disable this magnetic adsorption behavior through the `magnetic` attribute.
|
||||||
|
|
||||||
|
When `magnetic` is set to `false`, the panel will not automatically snap to anchor points after dragging, but it will still be constrained within the minimum and maximum boundaries defined by the anchors.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<van-floating-panel :anchors="[100, 200, 300]" :magnetic="false">
|
||||||
|
<div style="text-align: center; padding: 15px">
|
||||||
|
<p>Magnetic adsorption disabled</p>
|
||||||
|
<p>Panel can stop at any position within boundaries</p>
|
||||||
|
</div>
|
||||||
|
</van-floating-panel>
|
||||||
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
@@ -87,6 +102,7 @@ By default, both the header and content areas of FloatingPanel can be dragged, b
|
|||||||
| v-model:height | The current display height of the panel | _number \| string_ | `0` |
|
| v-model:height | The current display height of the panel | _number \| string_ | `0` |
|
||||||
| anchors | Setting custom anchors, unit `px` | _number[]_ | `[100, window.innerHeight * 0.6]` |
|
| anchors | Setting custom anchors, unit `px` | _number[]_ | `[100, window.innerHeight * 0.6]` |
|
||||||
| duration | Transition duration, unit second | _number \| string_ | `0.3` |
|
| 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` |
|
| content-draggable | Allow dragging content | _boolean_ | `true` |
|
||||||
| lock-scroll `v4.6.4` | When not dragging, Whether to lock background scroll | _boolean_ | `false` |
|
| 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` |
|
| safe-area-inset-bottom | Whether to enable bottom safe area adaptation | _boolean_ | `true` |
|
||||||
|
|||||||
@@ -78,6 +78,21 @@ export default {
|
|||||||
</van-floating-panel>
|
</van-floating-panel>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 禁用磁力吸附
|
||||||
|
|
||||||
|
默认情况下,拖拽结束后面板会自动吸附到最近的锚点。你可以通过 `magnetic` 属性来禁用这种磁力吸附行为。
|
||||||
|
|
||||||
|
当 `magnetic` 设置为 `false` 时,面板在拖拽结束后不会自动吸附到锚点,但仍然会被约束在锚点定义的最小和最大边界范围内。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<van-floating-panel :anchors="[100, 200, 300]" :magnetic="false">
|
||||||
|
<div style="text-align: center; padding: 15px">
|
||||||
|
<p>已禁用磁力吸附</p>
|
||||||
|
<p>面板可在边界范围内任意位置停留</p>
|
||||||
|
</div>
|
||||||
|
</van-floating-panel>
|
||||||
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
@@ -87,6 +102,7 @@ export default {
|
|||||||
| v-model:height | 当前面板的显示高度 | _number \| string_ | `0` |
|
| v-model:height | 当前面板的显示高度 | _number \| string_ | `0` |
|
||||||
| anchors | 设置自定义锚点, 单位 `px` | _number[]_ | `[100, window.innerHeight * 0.6]` |
|
| anchors | 设置自定义锚点, 单位 `px` | _number[]_ | `[100, window.innerHeight * 0.6]` |
|
||||||
| duration | 动画时长,单位秒,设置为 0 可以禁用动画 | _number \| string_ | `0.3` |
|
| duration | 动画时长,单位秒,设置为 0 可以禁用动画 | _number \| string_ | `0.3` |
|
||||||
|
| magnetic | 是否启用磁力吸附到锚点。禁用后面板可在锚点边界范围内任意位置停留 | _boolean_ | `true` |
|
||||||
| content-draggable | 允许拖拽内容容器 | _boolean_ | `true` |
|
| content-draggable | 允许拖拽内容容器 | _boolean_ | `true` |
|
||||||
| lock-scroll `v4.6.4` | 当不拖拽时,是否锁定背景滚动 | _boolean_ | `false` |
|
| lock-scroll `v4.6.4` | 当不拖拽时,是否锁定背景滚动 | _boolean_ | `false` |
|
||||||
| safe-area-inset-bottom | 是否开启[底部安全区适配](#/zh-CN/advanced-usage#di-bu-an-quan-qu-gua-pei) | _boolean_ | `true` |
|
| safe-area-inset-bottom | 是否开启[底部安全区适配](#/zh-CN/advanced-usage#di-bu-an-quan-qu-gua-pei) | _boolean_ | `true` |
|
||||||
|
|||||||
@@ -14,14 +14,18 @@ const t = useTranslate({
|
|||||||
'zh-CN': {
|
'zh-CN': {
|
||||||
customAnchors: '自定义锚点',
|
customAnchors: '自定义锚点',
|
||||||
headDragOnly: '仅头部拖拽',
|
headDragOnly: '仅头部拖拽',
|
||||||
|
disableMagnetic: '禁用吸附',
|
||||||
panelShowHeight: '面板显示高度',
|
panelShowHeight: '面板显示高度',
|
||||||
contentUnDrag: '内容不可拖拽',
|
contentUnDrag: '内容不可拖拽',
|
||||||
|
magneticDisabled: '已禁用磁力吸附,可在边界内任意停留',
|
||||||
},
|
},
|
||||||
'en-US': {
|
'en-US': {
|
||||||
customAnchors: 'Custom Anchors',
|
customAnchors: 'Custom Anchors',
|
||||||
headDragOnly: 'Head Drag Only',
|
headDragOnly: 'Head Drag Only',
|
||||||
|
disableMagnetic: 'Disable Magnetic',
|
||||||
panelShowHeight: 'Panel Show Height',
|
panelShowHeight: 'Panel Show Height',
|
||||||
contentUnDrag: 'Content cannot be dragged',
|
contentUnDrag: 'Content cannot be dragged',
|
||||||
|
magneticDisabled: 'Magnetic disabled, free positioning within boundaries',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,5 +68,13 @@ const height = ref(anchors[0]);
|
|||||||
</div>
|
</div>
|
||||||
</van-floating-panel>
|
</van-floating-panel>
|
||||||
</van-tab>
|
</van-tab>
|
||||||
|
|
||||||
|
<van-tab :title="t('disableMagnetic')">
|
||||||
|
<van-floating-panel :magnetic="false">
|
||||||
|
<div style="text-align: center; padding: 15px">
|
||||||
|
<p>{{ t('magneticDisabled') }}</p>
|
||||||
|
</div>
|
||||||
|
</van-floating-panel>
|
||||||
|
</van-tab>
|
||||||
</van-tabs>
|
</van-tabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ exports[`should render demo and match snapshot 1`] = `
|
|||||||
style="display:none;"
|
style="display:none;"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
id="van-tab"
|
||||||
|
role="tabpanel"
|
||||||
|
class="van-tab__panel"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="van-tabs-3"
|
||||||
|
data-allow-mismatch="attribute"
|
||||||
|
style="display:none;"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -47,6 +47,19 @@ exports[`should render demo and match snapshot 1`] = `
|
|||||||
Head Drag Only
|
Head Drag Only
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
id="van-tabs-3"
|
||||||
|
role="tab"
|
||||||
|
class="van-tab van-tab--line"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-selected="false"
|
||||||
|
aria-controls="van-tab"
|
||||||
|
data-allow-mismatch="attribute"
|
||||||
|
>
|
||||||
|
<span class="van-tab__text van-tab__text--ellipsis">
|
||||||
|
Disable Magnetic
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="van-tabs__line"
|
class="van-tabs__line"
|
||||||
style="transform: translateX(50px) translateX(-50%);"
|
style="transform: translateX(50px) translateX(-50%);"
|
||||||
@@ -280,6 +293,16 @@ exports[`should render demo and match snapshot 1`] = `
|
|||||||
style="display: none;"
|
style="display: none;"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
id="van-tab"
|
||||||
|
role="tabpanel"
|
||||||
|
class="van-tab__panel"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="van-tabs-3"
|
||||||
|
data-allow-mismatch="attribute"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -128,3 +128,45 @@ test('should render header slot correctly', () => {
|
|||||||
|
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not snap to anchors when magnetic is false', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<FloatingPanel anchors={[100, 200, 400]} magnetic={false}>
|
||||||
|
Content
|
||||||
|
</FloatingPanel>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial height should be 400 (highest anchor)
|
||||||
|
expect((wrapper.element as HTMLDivElement).style.height).toBe('400px');
|
||||||
|
|
||||||
|
// Drag to a position between anchors (down from 400 to around 250)
|
||||||
|
await triggerDrag(wrapper.find('.van-floating-panel__header'), 0, -150);
|
||||||
|
await later();
|
||||||
|
|
||||||
|
// Should stay at dragged position (around 250px), not snap to nearest anchor (200px)
|
||||||
|
const {transform} = (wrapper.element as HTMLDivElement).style;
|
||||||
|
expect(transform).not.toContain('-200px');
|
||||||
|
expect(transform).not.toContain('-400px');
|
||||||
|
expect(transform).toContain('-250px');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should snap to nearest anchor when magnetic is true (default)', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
render() {
|
||||||
|
return <FloatingPanel anchors={[100, 200, 400]}>Content</FloatingPanel>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag to trigger height change and snapping
|
||||||
|
await triggerDrag(wrapper.find('.van-floating-panel__header'), 0, 10);
|
||||||
|
await later();
|
||||||
|
|
||||||
|
// Should snap to nearest anchor (100px)
|
||||||
|
expect((wrapper.element as HTMLDivElement).style.transform).toContain(
|
||||||
|
'-100px',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user