✨(component): [dropdown]下拉面板位置自适应
This commit is contained in:
parent
aed6091707
commit
753f0f545b
@ -42,6 +42,6 @@
|
||||
background-color: var(--global-checked-color);
|
||||
color: white;
|
||||
}
|
||||
.layui-cascader.layui-dropdown-up > dl {
|
||||
min-width: unset;
|
||||
}
|
||||
// .layui-cascader.layui-dropdown-up > dl {
|
||||
// min-width: unset;
|
||||
// }
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<lay-dropdown class="layui-cascader" ref="dropdownRef">
|
||||
<lay-dropdown class="layui-cascader" ref="dropdownRef" :autoFitMinWidth="false">
|
||||
<lay-input
|
||||
v-model="displayValue"
|
||||
readonly
|
||||
|
@ -6,9 +6,7 @@
|
||||
.layui-dropdown dl {
|
||||
display: none;
|
||||
position: absolute;
|
||||
margin-top: 2px;
|
||||
z-index: 899;
|
||||
min-width: 100%;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
@ -6,37 +6,50 @@ export default {
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
import { provide, ref } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { DropdownTrigger } from "./interface";
|
||||
import { CSSProperties, nextTick, provide, ref, shallowRef, watch, watchEffect } from "vue";
|
||||
import { onClickOutside, useWindowSize } from "@vueuse/core";
|
||||
import { DropdownTrigger, dropdownPlacement } from "./interface";
|
||||
|
||||
export interface LayDropdownProps {
|
||||
trigger?: DropdownTrigger;
|
||||
placement?: dropdownPlacement;
|
||||
disabled?: boolean;
|
||||
autoFitPlacement?: boolean;
|
||||
autoFitWidth?: boolean;
|
||||
autoFitMinWidth?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayDropdownProps>(), {
|
||||
trigger: "click",
|
||||
disabled: false,
|
||||
placement: 'bottom-left',
|
||||
autoFitPlacement: true,
|
||||
autoFitMinWidth: true,
|
||||
autoFitWidth: false,
|
||||
});
|
||||
|
||||
const dropdownRef = shallowRef<null | HTMLElement>();
|
||||
const contentRef = shallowRef<null | HTMLElement>();
|
||||
const contentStyle = ref<CSSProperties>({});
|
||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||
const openState = ref(false);
|
||||
const dropdownRef = ref<null | HTMLElement>();
|
||||
const contentSpace = 2;
|
||||
|
||||
const emit = defineEmits(["open", "hide"]);
|
||||
|
||||
onClickOutside(dropdownRef, () => {
|
||||
openState.value = false;
|
||||
changeVisible(false)
|
||||
});
|
||||
|
||||
const open = (): void => {
|
||||
if (props.disabled == false) {
|
||||
openState.value = true;
|
||||
changeVisible(true);
|
||||
emit("open");
|
||||
}
|
||||
};
|
||||
|
||||
const hide = (): void => {
|
||||
openState.value = false;
|
||||
changeVisible(false);
|
||||
emit("hide");
|
||||
};
|
||||
|
||||
@ -49,26 +62,172 @@ const toggle = (): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const changeVisible = (visible: boolean) => {
|
||||
if (visible === openState.value) {
|
||||
return;
|
||||
}
|
||||
openState.value = visible;
|
||||
nextTick(() => {
|
||||
updateContentStyle();
|
||||
});
|
||||
}
|
||||
|
||||
const updateContentStyle = () => {
|
||||
if (!dropdownRef.value || !contentRef.value){
|
||||
return
|
||||
}
|
||||
const triggerRect = dropdownRef.value.getBoundingClientRect()
|
||||
const contentRect = contentRef.value.getBoundingClientRect()
|
||||
const { style } = getContentStyle(
|
||||
props.placement,
|
||||
triggerRect,
|
||||
contentRect,
|
||||
{autoFitPlacement: props.autoFitPlacement}
|
||||
);
|
||||
|
||||
if (props.autoFitMinWidth) {
|
||||
style.minWidth = `${triggerRect.width}px`;
|
||||
}
|
||||
if (props.autoFitWidth) {
|
||||
style.width = `${triggerRect.width}px`;
|
||||
}
|
||||
|
||||
contentStyle.value = style
|
||||
}
|
||||
|
||||
const getContentStyle = (
|
||||
placement: dropdownPlacement,
|
||||
triggerRect: DOMRect,
|
||||
contentRect: DOMRect,
|
||||
{
|
||||
autoFitPlacement = false,
|
||||
customStyle = {}
|
||||
}: {
|
||||
autoFitPlacement?: boolean;
|
||||
customStyle?: CSSProperties;
|
||||
} = {}
|
||||
) => {
|
||||
let { top, left } = getContentOffset(placement, triggerRect, contentRect)
|
||||
if(autoFitPlacement){
|
||||
const { top: fitTop, left: fitLeft } = getFitPlacement(top, left, placement, triggerRect, contentRect)
|
||||
top = fitTop;
|
||||
left = fitLeft;
|
||||
}
|
||||
const style = {
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
...customStyle,
|
||||
}
|
||||
return {
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
const getFitPlacement = (
|
||||
top: number,
|
||||
left: number,
|
||||
placement: dropdownPlacement,
|
||||
triggerRect: DOMRect,
|
||||
contentRect: DOMRect
|
||||
) => {
|
||||
// 溢出屏幕底部
|
||||
if (triggerRect.bottom + contentRect.height > windowHeight.value) {
|
||||
top = -contentRect.height - contentSpace
|
||||
}
|
||||
// 溢出屏幕顶部
|
||||
if (triggerRect.top - contentRect.height < 0) {
|
||||
top = triggerRect.height + contentSpace
|
||||
}
|
||||
if(["bottom-right", "top-right"].includes(placement) ){
|
||||
// 溢出屏幕左边
|
||||
const contentRectLeft = triggerRect.left - (contentRect.width - triggerRect.width)
|
||||
if (contentRectLeft < 0) {
|
||||
left = left + (0 - contentRectLeft)
|
||||
}
|
||||
}
|
||||
|
||||
if(["bottom-left", "top-left"].includes(placement)){
|
||||
// 溢出屏幕右边
|
||||
const contentRectRight = triggerRect.right + (contentRect.width - triggerRect.width)
|
||||
if (contentRectRight > windowWidth.value) {
|
||||
left = left - (contentRectRight - windowWidth.value)
|
||||
}
|
||||
}
|
||||
|
||||
if(["bottom", "top"].includes(placement)){
|
||||
const contentRectLeft = triggerRect.left - (contentRect.width - triggerRect.width) / 2
|
||||
const contentRectRight = triggerRect.right + (contentRect.width - triggerRect.width) / 2
|
||||
// 溢出屏幕左边
|
||||
if (contentRectLeft < 0) {
|
||||
left = left + (0 - contentRectLeft)
|
||||
}
|
||||
// 溢出屏幕右边
|
||||
if (contentRectRight > windowWidth.value) {
|
||||
left = left - (contentRectRight - windowWidth.value)
|
||||
}
|
||||
}
|
||||
return {
|
||||
top,
|
||||
left
|
||||
}
|
||||
}
|
||||
|
||||
const getContentOffset = (
|
||||
placement: dropdownPlacement,
|
||||
triggerRect: DOMRect,
|
||||
contentRect: DOMRect
|
||||
) => {
|
||||
switch (placement) {
|
||||
case "top":
|
||||
return {
|
||||
top: -contentRect.height - contentSpace,
|
||||
left: -(contentRect.width - triggerRect.width) / 2
|
||||
}
|
||||
case "top-left":
|
||||
return {
|
||||
top: -contentRect.height - contentSpace,
|
||||
left: 0
|
||||
}
|
||||
case "top-right":
|
||||
return {
|
||||
top: -contentRect.height - contentSpace,
|
||||
left: -(contentRect.width - triggerRect.width)
|
||||
}
|
||||
case "bottom":
|
||||
return {
|
||||
top: triggerRect.height + contentSpace,
|
||||
left: -(contentRect.width - triggerRect.width) / 2
|
||||
}
|
||||
case "bottom-left":
|
||||
return {
|
||||
top: triggerRect.height + contentSpace,
|
||||
left: 0
|
||||
}
|
||||
case "bottom-right":
|
||||
return {
|
||||
top: triggerRect.height + contentSpace,
|
||||
left: -(contentRect.width - triggerRect.width)
|
||||
}
|
||||
default:
|
||||
return {
|
||||
left: 0,
|
||||
top: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
provide("openState", openState);
|
||||
|
||||
defineExpose({ open, hide, toggle });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="dropdownRef"
|
||||
class="layui-dropdown"
|
||||
@mouseenter="trigger == 'hover' && open()"
|
||||
@mouseleave="trigger == 'hover' && hide()"
|
||||
:class="{ 'layui-dropdown-up': openState }"
|
||||
>
|
||||
<div
|
||||
@click="trigger == 'click' && toggle()"
|
||||
@contextmenu.prevent="trigger == 'contextMenu' && toggle()"
|
||||
>
|
||||
<div ref="dropdownRef" class="layui-dropdown" @mouseenter="trigger == 'hover' && open()"
|
||||
@mouseleave="trigger == 'hover' && hide()" :class="{ 'layui-dropdown-up': openState }">
|
||||
<div @click="trigger == 'click' && toggle()" @contextmenu.prevent="trigger == 'contextMenu' && toggle()">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<dl class="layui-anim layui-anim-upbit">
|
||||
<dl ref="contentRef" class="layui-anim layui-anim-upbit" :style="contentStyle">
|
||||
<slot name="content"></slot>
|
||||
</dl>
|
||||
</div>
|
||||
|
@ -1 +1,2 @@
|
||||
export type DropdownTrigger = "click" | "hover" | "contextMenu";
|
||||
export type dropdownPlacement = 'top' | 'top-left' | 'top-right' | 'bottom' | 'bottom-left' | 'bottom-right';
|
||||
|
@ -164,12 +164,69 @@ export default {
|
||||
::: demo
|
||||
|
||||
<template>
|
||||
<lay-dropdown>
|
||||
<lay-button type="primary">下拉菜单</lay-button>
|
||||
<lay-dropdown placement="top-left">
|
||||
<lay-button type="primary">topLeft</lay-button>
|
||||
<template #content>
|
||||
<div style="width:300px;height:200px;"></div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<lay-dropdown placement="top">
|
||||
<lay-button type="primary">top</lay-button>
|
||||
<template #content>
|
||||
<div style="width:300px;height:200px;"></div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<lay-dropdown placement="top-right">
|
||||
<lay-button type="primary">topRight</lay-button>
|
||||
<template #content>
|
||||
<div style="width:300px;height:200px;"></div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<lay-dropdown placement="bottom-left">
|
||||
<lay-button type="primary">bottomLeft</lay-button>
|
||||
<template #content>
|
||||
<div style="width:300px;height:200px;"></div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<lay-dropdown placement="bottom">
|
||||
<lay-button type="primary">bottom</lay-button>
|
||||
<template #content>
|
||||
<div style="width:300px;height:200px;"></div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<lay-dropdown placement="bottom-right">
|
||||
<lay-button type="primary">bottomRight</lay-button>
|
||||
<template #content>
|
||||
<div style="width:300px;height:200px;"></div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<lay-dropdown placement="bottom-left" :autoFitWidth="true">
|
||||
<lay-button type="primary">开启 autoFitWidth</lay-button>
|
||||
<template #content>
|
||||
<lay-dropdown-menu>
|
||||
<lay-dropdown-menu-item>选项一</lay-dropdown-menu-item>
|
||||
<lay-dropdown-menu-item>选项二1111111111111111111111</lay-dropdown-menu-item>
|
||||
<lay-dropdown-menu-item>选项三</lay-dropdown-menu-item>
|
||||
</lay-dropdown-menu>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
|
||||
<lay-dropdown placement="bottom-left" :autoFitMinWidth="false">
|
||||
<lay-button type="primary">关闭 autoFitMinWidth</lay-button>
|
||||
<template #content>
|
||||
<lay-dropdown-menu>
|
||||
<lay-dropdown-menu-item>选项一</lay-dropdown-menu-item>
|
||||
<lay-dropdown-menu-item>选项二111111111</lay-dropdown-menu-item>
|
||||
<lay-dropdown-menu-item>选项三</lay-dropdown-menu-item>
|
||||
</lay-dropdown-menu>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -195,6 +252,11 @@ export default {
|
||||
| ------- | -------- | --------------- |
|
||||
| trigger | 触发方式 | `click` `hover` `contextMenu` |
|
||||
| disabled | 是否禁用触发 | `true` `false` |
|
||||
| placement | 下拉面板位置 |`top` `top-left` `top-right` `bottom` `bottom-left` `bottom-right`|
|
||||
| autoFitPlacement| 是否自适应下拉面板位置,默认 `true` | `true` `false` |
|
||||
| autoFitWidth | 是否将下拉面板宽度设置为触发器宽度, 默认 `false` | `true` `false` |
|
||||
| autoFitMinWidth | 是否将下拉面板最小宽度设置为触发器宽度, 默认 `true` | `true` `false` |
|
||||
|
||||
|
||||
:::
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user