✨(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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user