✨(component): [dropdown]添加 visible,clickToClose,blueToClose 属性; hover 延时
This commit is contained in:
parent
45b23a5898
commit
e3c2b471b3
@ -6,14 +6,16 @@ export default {
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
import type { CSSProperties } from "vue";
|
||||||
import {
|
import {
|
||||||
CSSProperties,
|
computed,
|
||||||
nextTick,
|
nextTick,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onMounted,
|
onMounted,
|
||||||
provide,
|
provide,
|
||||||
ref,
|
ref,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import {
|
import {
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
@ -21,22 +23,28 @@ import {
|
|||||||
useThrottleFn,
|
useThrottleFn,
|
||||||
useWindowSize,
|
useWindowSize,
|
||||||
} from "@vueuse/core";
|
} from "@vueuse/core";
|
||||||
import { DropdownTrigger, dropdownPlacement } from "./interface";
|
import type { DropdownPlacement } from "./interface";
|
||||||
|
|
||||||
|
export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu";
|
||||||
|
|
||||||
export interface LayDropdownProps {
|
export interface LayDropdownProps {
|
||||||
trigger?: DropdownTrigger;
|
visible?: boolean;
|
||||||
placement?: dropdownPlacement;
|
trigger?: DropdownTrigger | DropdownTrigger[];
|
||||||
|
placement?: DropdownPlacement;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
autoFitPosition?: boolean;
|
autoFitPosition?: boolean;
|
||||||
autoFitWidth?: boolean;
|
autoFitWidth?: boolean;
|
||||||
autoFitMinWidth?: boolean;
|
autoFitMinWidth?: boolean;
|
||||||
updateAtScroll?: boolean;
|
updateAtScroll?: boolean;
|
||||||
autoFixPosition?: boolean;
|
autoFixPosition?: boolean;
|
||||||
|
clickToClose?: boolean;
|
||||||
|
blurToClose?: boolean;
|
||||||
clickOutsideToClose?: boolean;
|
clickOutsideToClose?: boolean;
|
||||||
contentOffset?: number;
|
contentOffset?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<LayDropdownProps>(), {
|
const props = withDefaults(defineProps<LayDropdownProps>(), {
|
||||||
|
visible: false,
|
||||||
trigger: "click",
|
trigger: "click",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
placement: "bottom-left",
|
placement: "bottom-left",
|
||||||
@ -45,6 +53,8 @@ const props = withDefaults(defineProps<LayDropdownProps>(), {
|
|||||||
autoFitWidth: false,
|
autoFitWidth: false,
|
||||||
updateAtScroll: false,
|
updateAtScroll: false,
|
||||||
autoFixPosition: true,
|
autoFixPosition: true,
|
||||||
|
clickToClose: true,
|
||||||
|
blurToClose: true,
|
||||||
clickOutsideToClose: true,
|
clickOutsideToClose: true,
|
||||||
contentOffset: 2,
|
contentOffset: 2,
|
||||||
});
|
});
|
||||||
@ -55,23 +65,30 @@ const contentStyle = ref<CSSProperties>({});
|
|||||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||||
const openState = ref(false);
|
const openState = ref(false);
|
||||||
|
|
||||||
|
const triggerMethods = computed(() =>
|
||||||
|
([] as Array<DropdownTrigger>).concat(props.trigger)
|
||||||
|
);
|
||||||
|
|
||||||
const emit = defineEmits(["open", "hide"]);
|
const emit = defineEmits(["open", "hide"]);
|
||||||
|
|
||||||
onClickOutside(dropdownRef, () => {
|
let delayTimer = 0;
|
||||||
if (props.clickOutsideToClose) {
|
|
||||||
changeVisible(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const open = (): void => {
|
const cleanDelayTimer = () => {
|
||||||
|
if (delayTimer) {
|
||||||
|
window.clearTimeout(delayTimer);
|
||||||
|
delayTimer = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = (delay?: number): void => {
|
||||||
if (props.disabled == false) {
|
if (props.disabled == false) {
|
||||||
changeVisible(true);
|
changeVisible(true, delay);
|
||||||
emit("open");
|
emit("open");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hide = (): void => {
|
const hide = (delay?: number): void => {
|
||||||
changeVisible(false);
|
changeVisible(false, delay);
|
||||||
emit("hide");
|
emit("hide");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,14 +101,25 @@ const toggle = (): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeVisible = (visible: boolean) => {
|
const changeVisible = (visible: boolean, delay?: number) => {
|
||||||
if (visible === openState.value) {
|
if (visible === openState.value && delayTimer === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openState.value = visible;
|
const update = () => {
|
||||||
nextTick(() => {
|
openState.value = visible;
|
||||||
updateContentStyle();
|
nextTick(() => {
|
||||||
});
|
updateContentStyle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(delay){
|
||||||
|
cleanDelayTimer();
|
||||||
|
if (visible !== openState.value) {
|
||||||
|
delayTimer = window.setTimeout(update, delay);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
update();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateContentStyle = () => {
|
const updateContentStyle = () => {
|
||||||
@ -115,7 +143,7 @@ const updateContentStyle = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getContentStyle = (
|
const getContentStyle = (
|
||||||
placement: dropdownPlacement,
|
placement: DropdownPlacement,
|
||||||
triggerRect: DOMRect,
|
triggerRect: DOMRect,
|
||||||
contentRect: DOMRect,
|
contentRect: DOMRect,
|
||||||
{
|
{
|
||||||
@ -151,7 +179,7 @@ const getContentStyle = (
|
|||||||
const getFitPlacement = (
|
const getFitPlacement = (
|
||||||
top: number,
|
top: number,
|
||||||
left: number,
|
left: number,
|
||||||
placement: dropdownPlacement,
|
placement: DropdownPlacement,
|
||||||
triggerRect: DOMRect,
|
triggerRect: DOMRect,
|
||||||
contentRect: DOMRect
|
contentRect: DOMRect
|
||||||
) => {
|
) => {
|
||||||
@ -202,7 +230,7 @@ const getFitPlacement = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getContentOffset = (
|
const getContentOffset = (
|
||||||
placement: dropdownPlacement,
|
placement: DropdownPlacement,
|
||||||
triggerRect: DOMRect,
|
triggerRect: DOMRect,
|
||||||
contentRect: DOMRect
|
contentRect: DOMRect
|
||||||
) => {
|
) => {
|
||||||
@ -268,7 +296,47 @@ const handleScroll = useThrottleFn(() => {
|
|||||||
if (openState.value) {
|
if (openState.value) {
|
||||||
updateContentStyle();
|
updateContentStyle();
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 250);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (props.disabled || (openState.value && !props.clickToClose)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (triggerMethods.value.includes("click") || triggerMethods.value.includes("contextMenu")){
|
||||||
|
toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
if (props.disabled || !triggerMethods.value.includes('hover')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
if (props.disabled || !triggerMethods.value.includes('hover')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hide(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFocusin = () => {
|
||||||
|
if (props.disabled || !triggerMethods.value.includes('focus')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFocusout = () => {
|
||||||
|
if (props.disabled || !triggerMethods.value.includes('focus')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!props.blurToClose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
|
||||||
const { stop: removeContentResizeObserver } = useResizeObserver(
|
const { stop: removeContentResizeObserver } = useResizeObserver(
|
||||||
contentRef,
|
contentRef,
|
||||||
@ -288,6 +356,12 @@ const { stop: removeTriggerResizeObserver } = useResizeObserver(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onClickOutside(dropdownRef, () => {
|
||||||
|
if (props.clickOutsideToClose) {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let scrollElements: HTMLElement[] | undefined;
|
let scrollElements: HTMLElement[] | undefined;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -306,11 +380,19 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
scrollElements = undefined;
|
scrollElements = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeContentResizeObserver();
|
removeContentResizeObserver();
|
||||||
removeTriggerResizeObserver();
|
removeTriggerResizeObserver();
|
||||||
}),
|
})
|
||||||
provide("openState", openState);
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
openState.value = newVal;
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
provide("openState", openState);
|
||||||
|
|
||||||
defineExpose({ open, hide, toggle });
|
defineExpose({ open, hide, toggle });
|
||||||
</script>
|
</script>
|
||||||
@ -319,13 +401,15 @@ defineExpose({ open, hide, toggle });
|
|||||||
<div
|
<div
|
||||||
ref="dropdownRef"
|
ref="dropdownRef"
|
||||||
class="layui-dropdown"
|
class="layui-dropdown"
|
||||||
@mouseenter="trigger == 'hover' && open()"
|
@mouseenter="handleMouseEnter()"
|
||||||
@mouseleave="trigger == 'hover' && hide()"
|
@mouseleave="handleMouseLeave()"
|
||||||
|
@focusin="handleFocusin()"
|
||||||
|
@focusout="handleFocusout()"
|
||||||
:class="{ 'layui-dropdown-up': openState }"
|
:class="{ 'layui-dropdown-up': openState }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@click="trigger == 'click' && toggle()"
|
@click="handleClick()"
|
||||||
@contextmenu.prevent="trigger == 'contextMenu' && toggle()"
|
@contextmenu.prevent="handleClick()"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export type DropdownTrigger = "click" | "hover" | "contextMenu";
|
export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu";
|
||||||
export type dropdownPlacement =
|
export type DropdownPlacement =
|
||||||
| "top"
|
| "top"
|
||||||
| "top-left"
|
| "top-left"
|
||||||
| "top-right"
|
| "top-right"
|
||||||
|
@ -293,15 +293,18 @@ export default {
|
|||||||
|
|
||||||
| 属性 | 描述 | 可选值 |
|
| 属性 | 描述 | 可选值 |
|
||||||
| ------- | -------- | --------------- |
|
| ------- | -------- | --------------- |
|
||||||
| trigger | 触发方式 | `click` `hover` `contextMenu` |
|
| visible | 下拉面板是否可见 |`true` `false`|
|
||||||
|
| trigger | 触发方式,类型 `string` 或 trigger 数组 | `click` `hover` `focus` `contextMenu` |
|
||||||
| disabled | 是否禁用触发 | `true` `false` |
|
| disabled | 是否禁用触发 | `true` `false` |
|
||||||
| placement | 下拉面板位置 |`top` `top-left` `top-right` `bottom` `bottom-left` `bottom-right`|
|
| placement | 下拉面板位置 |`top` `top-left` `top-right` `bottom` `bottom-left` `bottom-right`|
|
||||||
| autoFitPosition| 是否自动调整下拉面板位置,默认 `true` | `true` `false` |
|
| autoFitPosition| 是否自动调整下拉面板位置,默认 `true` |`true` `false` |
|
||||||
| autoFitWidth | 是否将下拉面板宽度设置为触发器宽度, 默认 `false` | `true` `false` |
|
| autoFitWidth | 是否将下拉面板宽度设置为触发器宽度, 默认 `false` |`true` `false` |
|
||||||
| autoFitMinWidth | 是否将下拉面板最小宽度设置为触发器宽度, 默认 `true` | `true` `false` |
|
| autoFitMinWidth | 是否将下拉面板最小宽度设置为触发器宽度, 默认 `true` |`true` `false` |
|
||||||
| updateAtScroll | 是否在容器滚动时更新下拉面板的位置,默认 `false` | `true` `false` |
|
| updateAtScroll | 是否在容器滚动时更新下拉面板的位置,默认 `false` | `true` `false` |
|
||||||
| autoFixPosition | 是否在触发器或下拉面板尺寸变化时更新下拉面板位置,面板尺寸变化参见级联选择器,默认 `true` | `true` `false` |
|
| autoFixPosition | 是否在触发器或下拉面板尺寸变化时更新下拉面板位置,面板尺寸变化参见级联选择器,默认 `true` |`true` `false` |
|
||||||
| clickOutsideToClose| 是否点击外部关闭下拉面板,默认 `true`| `true` `false`|
|
| clickToClose | 是否在点击触发器时关闭面板 |`true` `false`|
|
||||||
|
| blurToClose | 是否在触发器失去焦点时关闭面板 |`true` `false`|
|
||||||
|
| clickOutsideToClose| 是否点击外部关闭下拉面板,默认 `true`|`true` `false`|
|
||||||
| contentOffset | 下拉面板距离触发器的偏移距离,默认 2| -|
|
| contentOffset | 下拉面板距离触发器的偏移距离,默认 2| -|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user