✨(component): [dropdown]新增 contentClass 属性
*新增 contentClass *新增 contentStyle *新增 alignPoint
This commit is contained in:
		
							parent
							
								
									106aadf788
								
							
						
					
					
						commit
						0fe5aa2644
					
				@ -44,7 +44,7 @@ import {
 | 
			
		||||
  DropdownContext,
 | 
			
		||||
} from "./interface";
 | 
			
		||||
import TeleportWrapper from "../_components/teleportWrapper.vue";
 | 
			
		||||
import { useFirstElement } from "./useFirstElement";
 | 
			
		||||
import { useFirstElement, isScrollElement, getScrollElements } from "./util";
 | 
			
		||||
import RenderFunction from "../_components/renderFunction";
 | 
			
		||||
import { transformPlacement } from "./util";
 | 
			
		||||
 | 
			
		||||
@ -67,11 +67,11 @@ export interface LayDropdownProps {
 | 
			
		||||
  mouseEnterDelay?: number;
 | 
			
		||||
  mouseLeaveDelay?: number;
 | 
			
		||||
  focusDelay?: number;
 | 
			
		||||
  // 未完善,暂不开放
 | 
			
		||||
  alignPoint?: boolean;
 | 
			
		||||
  popupContainer?: string | undefined;
 | 
			
		||||
  contentClass?: string | Array<string | object> | object;
 | 
			
		||||
  contentStyle?: StyleValue;
 | 
			
		||||
  // wip
 | 
			
		||||
  popupContainer?: string | undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<LayDropdownProps>(), {
 | 
			
		||||
@ -445,25 +445,6 @@ const getContentOffset = (
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isScrollElement = (element: HTMLElement) => {
 | 
			
		||||
  return (
 | 
			
		||||
    element.scrollHeight > element.offsetHeight ||
 | 
			
		||||
    element.scrollWidth > element.offsetWidth
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getScrollElements = (container: HTMLElement | undefined) => {
 | 
			
		||||
  const scrollElements: HTMLElement[] = [];
 | 
			
		||||
  let element: HTMLElement | undefined = container;
 | 
			
		||||
  while (element && element !== document.documentElement) {
 | 
			
		||||
    if (isScrollElement(element)) {
 | 
			
		||||
      scrollElements.push(element);
 | 
			
		||||
    }
 | 
			
		||||
    element = element.parentElement ?? undefined;
 | 
			
		||||
  }
 | 
			
		||||
  return scrollElements;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleScroll = useThrottleFn(() => {
 | 
			
		||||
  if (openState.value) {
 | 
			
		||||
    updateContentStyle();
 | 
			
		||||
 | 
			
		||||
@ -1,103 +0,0 @@
 | 
			
		||||
import { Component, onMounted, onUpdated, ref, VNode, VNodeTypes } from "vue";
 | 
			
		||||
 | 
			
		||||
export interface SlotChildren {
 | 
			
		||||
  value?: VNode[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Quoted from arco-vue
 | 
			
		||||
// https://github.com/arco-design/arco-design-vue/blob/main/packages/web-vue/components/_utils/vue-utils.ts
 | 
			
		||||
export enum ShapeFlags {
 | 
			
		||||
  ELEMENT = 1,
 | 
			
		||||
  FUNCTIONAL_COMPONENT = 1 << 1,
 | 
			
		||||
  STATEFUL_COMPONENT = 1 << 2,
 | 
			
		||||
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
 | 
			
		||||
  TEXT_CHILDREN = 1 << 3,
 | 
			
		||||
  ARRAY_CHILDREN = 1 << 4,
 | 
			
		||||
  SLOTS_CHILDREN = 1 << 5,
 | 
			
		||||
  TELEPORT = 1 << 6,
 | 
			
		||||
  SUSPENSE = 1 << 7,
 | 
			
		||||
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
 | 
			
		||||
  COMPONENT_KEPT_ALIVE = 1 << 9,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const isElement = (vn: VNode) => {
 | 
			
		||||
  return Boolean(vn && vn.shapeFlag & ShapeFlags.ELEMENT);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isComponent = (
 | 
			
		||||
  vn: VNode,
 | 
			
		||||
  type?: VNodeTypes
 | 
			
		||||
): type is Component => {
 | 
			
		||||
  return Boolean(vn && vn.shapeFlag & ShapeFlags.COMPONENT);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isArrayChildren = (
 | 
			
		||||
  vn: VNode,
 | 
			
		||||
  children: VNode["children"]
 | 
			
		||||
): children is VNode[] => {
 | 
			
		||||
  return Boolean(vn && vn.shapeFlag & ShapeFlags.ARRAY_CHILDREN);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getChildrenArray = (vn: VNode): VNode[] | undefined => {
 | 
			
		||||
  if (isArrayChildren(vn, vn.children)) {
 | 
			
		||||
    return vn.children;
 | 
			
		||||
  }
 | 
			
		||||
  if (Array.isArray(vn)) {
 | 
			
		||||
    return vn;
 | 
			
		||||
  }
 | 
			
		||||
  return undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getFirstElementFromVNode = (
 | 
			
		||||
  vn: VNode
 | 
			
		||||
): HTMLElement | undefined => {
 | 
			
		||||
  if (isElement(vn)) {
 | 
			
		||||
    return vn.el as HTMLElement;
 | 
			
		||||
  }
 | 
			
		||||
  if (isComponent(vn)) {
 | 
			
		||||
    if ((vn.el as Node)?.nodeType === 1) {
 | 
			
		||||
      return vn.el as HTMLElement;
 | 
			
		||||
    }
 | 
			
		||||
    if (vn.component?.subTree) {
 | 
			
		||||
      const ele = getFirstElementFromVNode(vn.component.subTree);
 | 
			
		||||
      if (ele) return ele;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    const children = getChildrenArray(vn);
 | 
			
		||||
    return getFirstElementFromChildren(children);
 | 
			
		||||
  }
 | 
			
		||||
  return undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getFirstElementFromChildren = (
 | 
			
		||||
  children: VNode[] | undefined
 | 
			
		||||
): HTMLElement | undefined => {
 | 
			
		||||
  if (children && children.length > 0) {
 | 
			
		||||
    for (const child of children) {
 | 
			
		||||
      const element = getFirstElementFromVNode(child);
 | 
			
		||||
      if (element) return element;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useFirstElement = () => {
 | 
			
		||||
  const children: SlotChildren = {};
 | 
			
		||||
  const firstElement = ref<HTMLElement>();
 | 
			
		||||
 | 
			
		||||
  const getFirstElement = () => {
 | 
			
		||||
    const element = getFirstElementFromChildren(children.value);
 | 
			
		||||
    if (element !== firstElement.value) {
 | 
			
		||||
      firstElement.value = element;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  onMounted(() => getFirstElement());
 | 
			
		||||
 | 
			
		||||
  onUpdated(() => getFirstElement());
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    children,
 | 
			
		||||
    firstElement,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -1,5 +1,128 @@
 | 
			
		||||
import { DropdownPlacement } from "./interface";
 | 
			
		||||
 | 
			
		||||
import { Component, onMounted, onUpdated, ref, VNode, VNodeTypes } from "vue";
 | 
			
		||||
 | 
			
		||||
export interface SlotChildren {
 | 
			
		||||
  value?: VNode[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Quoted from arco-vue
 | 
			
		||||
// https://github.com/arco-design/arco-design-vue/blob/main/packages/web-vue/components/_utils/vue-utils.ts
 | 
			
		||||
export enum ShapeFlags {
 | 
			
		||||
  ELEMENT = 1,
 | 
			
		||||
  FUNCTIONAL_COMPONENT = 1 << 1,
 | 
			
		||||
  STATEFUL_COMPONENT = 1 << 2,
 | 
			
		||||
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
 | 
			
		||||
  TEXT_CHILDREN = 1 << 3,
 | 
			
		||||
  ARRAY_CHILDREN = 1 << 4,
 | 
			
		||||
  SLOTS_CHILDREN = 1 << 5,
 | 
			
		||||
  TELEPORT = 1 << 6,
 | 
			
		||||
  SUSPENSE = 1 << 7,
 | 
			
		||||
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
 | 
			
		||||
  COMPONENT_KEPT_ALIVE = 1 << 9,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const isScrollElement = (element: HTMLElement) => {
 | 
			
		||||
  return (
 | 
			
		||||
    element.scrollHeight > element.offsetHeight ||
 | 
			
		||||
    element.scrollWidth > element.offsetWidth
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getScrollElements = (container: HTMLElement | undefined) => {
 | 
			
		||||
  const scrollElements: HTMLElement[] = [];
 | 
			
		||||
  let element: HTMLElement | undefined = container;
 | 
			
		||||
  while (element && element !== document.documentElement) {
 | 
			
		||||
    if (isScrollElement(element)) {
 | 
			
		||||
      scrollElements.push(element);
 | 
			
		||||
    }
 | 
			
		||||
    element = element.parentElement ?? undefined;
 | 
			
		||||
  }
 | 
			
		||||
  return scrollElements;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isElement = (vn: VNode) => {
 | 
			
		||||
  return Boolean(vn && vn.shapeFlag & ShapeFlags.ELEMENT);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isComponent = (
 | 
			
		||||
  vn: VNode,
 | 
			
		||||
  type?: VNodeTypes
 | 
			
		||||
): type is Component => {
 | 
			
		||||
  return Boolean(vn && vn.shapeFlag & ShapeFlags.COMPONENT);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isArrayChildren = (
 | 
			
		||||
  vn: VNode,
 | 
			
		||||
  children: VNode["children"]
 | 
			
		||||
): children is VNode[] => {
 | 
			
		||||
  return Boolean(vn && vn.shapeFlag & ShapeFlags.ARRAY_CHILDREN);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getChildrenArray = (vn: VNode): VNode[] | undefined => {
 | 
			
		||||
  if (isArrayChildren(vn, vn.children)) {
 | 
			
		||||
    return vn.children;
 | 
			
		||||
  }
 | 
			
		||||
  if (Array.isArray(vn)) {
 | 
			
		||||
    return vn;
 | 
			
		||||
  }
 | 
			
		||||
  return undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getFirstElementFromVNode = (
 | 
			
		||||
  vn: VNode
 | 
			
		||||
): HTMLElement | undefined => {
 | 
			
		||||
  if (isElement(vn)) {
 | 
			
		||||
    return vn.el as HTMLElement;
 | 
			
		||||
  }
 | 
			
		||||
  if (isComponent(vn)) {
 | 
			
		||||
    if ((vn.el as Node)?.nodeType === 1) {
 | 
			
		||||
      return vn.el as HTMLElement;
 | 
			
		||||
    }
 | 
			
		||||
    if (vn.component?.subTree) {
 | 
			
		||||
      const ele = getFirstElementFromVNode(vn.component.subTree);
 | 
			
		||||
      if (ele) return ele;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    const children = getChildrenArray(vn);
 | 
			
		||||
    return getFirstElementFromChildren(children);
 | 
			
		||||
  }
 | 
			
		||||
  return undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getFirstElementFromChildren = (
 | 
			
		||||
  children: VNode[] | undefined
 | 
			
		||||
): HTMLElement | undefined => {
 | 
			
		||||
  if (children && children.length > 0) {
 | 
			
		||||
    for (const child of children) {
 | 
			
		||||
      const element = getFirstElementFromVNode(child);
 | 
			
		||||
      if (element) return element;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useFirstElement = () => {
 | 
			
		||||
  const children: SlotChildren = {};
 | 
			
		||||
  const firstElement = ref<HTMLElement>();
 | 
			
		||||
 | 
			
		||||
  const getFirstElement = () => {
 | 
			
		||||
    const element = getFirstElementFromChildren(children.value);
 | 
			
		||||
    if (element !== firstElement.value) {
 | 
			
		||||
      firstElement.value = element;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  onMounted(() => getFirstElement());
 | 
			
		||||
 | 
			
		||||
  onUpdated(() => getFirstElement());
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    children,
 | 
			
		||||
    firstElement,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const transformPlacement = (
 | 
			
		||||
  placement: DropdownPlacement
 | 
			
		||||
): DropdownPlacement => {
 | 
			
		||||
 | 
			
		||||
@ -471,45 +471,14 @@ export default {
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
::: title 其它属性
 | 
			
		||||
::: title 跟随鼠标
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
::: demo
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<lay-space>
 | 
			
		||||
   <lay-dropdown placement="bottom-start" autoFitWidth updateAtScroll>
 | 
			
		||||
    <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-start" :autoFitMinWidth="false" updateAtScroll>
 | 
			
		||||
    <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>
 | 
			
		||||
  <lay-dropdown placement="bottom-start" updateAtScroll>
 | 
			
		||||
    <lay-button type="primary">updateAtScroll</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>
 | 
			
		||||
  <lay-dropdown placement="bottom-start" updateAtScroll :contentOffset="8">
 | 
			
		||||
    <lay-button type="primary">contentOffset: 8px</lay-button>
 | 
			
		||||
  <lay-dropdown ref="dropdownRefEl" :trigger="['click','contextMenu']" alignPoint>
 | 
			
		||||
    <div style="width:500px;height:300px;background-color:#eee"></div>
 | 
			
		||||
    <template #content>
 | 
			
		||||
        <lay-dropdown-menu>
 | 
			
		||||
          <lay-dropdown-menu-item>选项一</lay-dropdown-menu-item>
 | 
			
		||||
@ -518,15 +487,32 @@ export default {
 | 
			
		||||
        </lay-dropdown-menu>
 | 
			
		||||
    </template>
 | 
			
		||||
  </lay-dropdown>
 | 
			
		||||
</lay-space>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, computed } from 'vue'
 | 
			
		||||
<script>
 | 
			
		||||
import { ref, onMounted } from 'vue'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  setup() {
 | 
			
		||||
    const dropdownRefEl = ref(null);
 | 
			
		||||
    const handleScroll = () => {
 | 
			
		||||
      dropdownRefEl.value.hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onMounted(() => {
 | 
			
		||||
      const scrollEl =  document.querySelector(".layui-body")
 | 
			
		||||
      scrollEl.addEventListener("scroll", handleScroll)
 | 
			
		||||
    })
 | 
			
		||||
    return {
 | 
			
		||||
      dropdownRefEl
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
::: title Dropdown 属性
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
@ -550,7 +536,9 @@ import { ref, computed } from 'vue'
 | 
			
		||||
| mouseEnterDelay     | mouseEnter 事件延迟触发的时间, trigger hover 有效 | `number`  | `150`    | -                                               |
 | 
			
		||||
| mouseLeaveDelay     | mouseLeave 事件延迟触发的时间, trigger hover 有效 | `number`  | `150`    | -                                               |
 | 
			
		||||
| focusDelay          | focus 事件延迟触发的时间, trigger focus 有效      | `number`  | `150`    | -                                               |
 | 
			
		||||
 | 
			
		||||
| alignPoint |跟随鼠标|`boolean`|`false`|`true` `false`|
 | 
			
		||||
| contentClass| 弹出内容的类名  | `string`| -| -|
 | 
			
		||||
| contentStyle| 弹出内容的样式  | `string` | - | -|
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user