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