✨(component): [dropdown]滚动、触发器或面板尺寸变化时更新下拉面板位置; clickOutsideToClose 属性
This commit is contained in:
		
							parent
							
								
									04035e9a4e
								
							
						
					
					
						commit
						49b0d96b48
					
				@ -9,33 +9,46 @@ import "./index.less";
 | 
			
		||||
import {
 | 
			
		||||
  CSSProperties,
 | 
			
		||||
  nextTick,
 | 
			
		||||
  onBeforeUnmount,
 | 
			
		||||
  onMounted,
 | 
			
		||||
  provide,
 | 
			
		||||
  ref,
 | 
			
		||||
  shallowRef,
 | 
			
		||||
} from "vue";
 | 
			
		||||
import { onClickOutside, useResizeObserver, useScroll, useWindowSize } from "@vueuse/core";
 | 
			
		||||
import {
 | 
			
		||||
  onClickOutside,
 | 
			
		||||
  useResizeObserver,
 | 
			
		||||
  useThrottleFn,
 | 
			
		||||
  useWindowSize,
 | 
			
		||||
} from "@vueuse/core";
 | 
			
		||||
import { DropdownTrigger, dropdownPlacement } from "./interface";
 | 
			
		||||
 | 
			
		||||
export interface LayDropdownProps {
 | 
			
		||||
  trigger?: DropdownTrigger;
 | 
			
		||||
  placement?: dropdownPlacement;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  autoFitPlacement?: boolean;
 | 
			
		||||
  autoFitPosition?: boolean;
 | 
			
		||||
  autoFitWidth?: boolean;
 | 
			
		||||
  autoFitMinWidth?: boolean;
 | 
			
		||||
  updateAtScroll?: boolean;
 | 
			
		||||
  autoFixPosition?: boolean;
 | 
			
		||||
  clickOutsideToClose?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<LayDropdownProps>(), {
 | 
			
		||||
  trigger: "click",
 | 
			
		||||
  disabled: false,
 | 
			
		||||
  placement: "bottom-left",
 | 
			
		||||
  autoFitPlacement: true,
 | 
			
		||||
  autoFitPosition: true,
 | 
			
		||||
  autoFitMinWidth: true,
 | 
			
		||||
  autoFitWidth: false,
 | 
			
		||||
  updateAtScroll: false,
 | 
			
		||||
  autoFixPosition: true,
 | 
			
		||||
  clickOutsideToClose: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dropdownRef = shallowRef<null | HTMLElement>();
 | 
			
		||||
const contentRef = shallowRef<null | HTMLElement>();
 | 
			
		||||
const dropdownRef = shallowRef<HTMLElement | undefined>();
 | 
			
		||||
const contentRef = shallowRef<HTMLElement | undefined>();
 | 
			
		||||
const contentStyle = ref<CSSProperties>({});
 | 
			
		||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
 | 
			
		||||
const openState = ref(false);
 | 
			
		||||
@ -44,7 +57,9 @@ const contentSpace = 2;
 | 
			
		||||
const emit = defineEmits(["open", "hide"]);
 | 
			
		||||
 | 
			
		||||
onClickOutside(dropdownRef, () => {
 | 
			
		||||
  changeVisible(false);
 | 
			
		||||
  if (props.clickOutsideToClose) {
 | 
			
		||||
    changeVisible(false);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const open = (): void => {
 | 
			
		||||
@ -85,7 +100,7 @@ const updateContentStyle = () => {
 | 
			
		||||
  const triggerRect = dropdownRef.value.getBoundingClientRect();
 | 
			
		||||
  const contentRect = contentRef.value.getBoundingClientRect();
 | 
			
		||||
  const { style } = getContentStyle(props.placement, triggerRect, contentRect, {
 | 
			
		||||
    autoFitPlacement: props.autoFitPlacement,
 | 
			
		||||
    autoFitPosition: props.autoFitPosition,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (props.autoFitMinWidth) {
 | 
			
		||||
@ -103,15 +118,15 @@ const getContentStyle = (
 | 
			
		||||
  triggerRect: DOMRect,
 | 
			
		||||
  contentRect: DOMRect,
 | 
			
		||||
  {
 | 
			
		||||
    autoFitPlacement = false,
 | 
			
		||||
    autoFitPosition = false,
 | 
			
		||||
    customStyle = {},
 | 
			
		||||
  }: {
 | 
			
		||||
    autoFitPlacement?: boolean;
 | 
			
		||||
    autoFitPosition?: boolean;
 | 
			
		||||
    customStyle?: CSSProperties;
 | 
			
		||||
  } = {}
 | 
			
		||||
) => {
 | 
			
		||||
  let { top, left } = getContentOffset(placement, triggerRect, contentRect);
 | 
			
		||||
  if (autoFitPlacement) {
 | 
			
		||||
  if (autoFitPosition) {
 | 
			
		||||
    const { top: fitTop, left: fitLeft } = getFitPlacement(
 | 
			
		||||
      top,
 | 
			
		||||
      left,
 | 
			
		||||
@ -229,13 +244,72 @@ const getContentOffset = (
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
useResizeObserver(contentRef, () => {
 | 
			
		||||
  if (openState.value) {
 | 
			
		||||
    updateContentStyle()
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const isScrollElement = (element: HTMLElement) => {
 | 
			
		||||
  return (
 | 
			
		||||
    element.scrollHeight > element.offsetHeight ||
 | 
			
		||||
    element.scrollWidth > element.offsetWidth
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
provide("openState", openState);
 | 
			
		||||
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();
 | 
			
		||||
  }
 | 
			
		||||
}, 300);
 | 
			
		||||
 | 
			
		||||
const { stop: removeContentResizeObserver } = useResizeObserver(
 | 
			
		||||
  contentRef,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (openState.value && props.autoFixPosition) {
 | 
			
		||||
      updateContentStyle();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const { stop: removeTriggerResizeObserver } = useResizeObserver(
 | 
			
		||||
  dropdownRef,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (openState.value && props.autoFixPosition) {
 | 
			
		||||
      updateContentStyle();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
let scrollElements: HTMLElement[] | undefined;
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (props.updateAtScroll) {
 | 
			
		||||
    scrollElements = getScrollElements(dropdownRef.value);
 | 
			
		||||
    for (const item of scrollElements) {
 | 
			
		||||
      item.addEventListener("scroll", handleScroll);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  if (scrollElements) {
 | 
			
		||||
    for (const item of scrollElements) {
 | 
			
		||||
      item.removeEventListener("scroll", handleScroll);
 | 
			
		||||
    }
 | 
			
		||||
    scrollElements = undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeContentResizeObserver();
 | 
			
		||||
  removeTriggerResizeObserver();
 | 
			
		||||
}),
 | 
			
		||||
  provide("openState", openState);
 | 
			
		||||
 | 
			
		||||
defineExpose({ open, hide, toggle });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -206,8 +206,9 @@ export default {
 | 
			
		||||
    </template>
 | 
			
		||||
  </lay-dropdown>
 | 
			
		||||
    
 | 
			
		||||
  <lay-dropdown placement="bottom-left" :autoFitWidth="true">
 | 
			
		||||
    <lay-button type="primary">开启 autoFitWidth</lay-button>
 | 
			
		||||
  <br><br>
 | 
			
		||||
  <lay-dropdown placement="bottom-left" autoFitWidth>
 | 
			
		||||
    <lay-button type="primary">autoFitWidth</lay-button>
 | 
			
		||||
    <template #content>
 | 
			
		||||
      <lay-dropdown-menu>
 | 
			
		||||
        <lay-dropdown-menu-item>选项一</lay-dropdown-menu-item>
 | 
			
		||||
@ -227,6 +228,30 @@ export default {
 | 
			
		||||
      </lay-dropdown-menu> 
 | 
			
		||||
    </template>
 | 
			
		||||
  </lay-dropdown>
 | 
			
		||||
      
 | 
			
		||||
  <lay-dropdown placement="bottom-left" 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-left" :autoFixPosition="true" :clickOutsideToClose="false">
 | 
			
		||||
    <lay-button type="primary" :size="btnSize">autoFixPosition</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-button  @click="toogleSize">切换左边按钮</lay-button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@ -235,7 +260,14 @@ import { ref } from 'vue'
 | 
			
		||||
export default {
 | 
			
		||||
  setup() {
 | 
			
		||||
 | 
			
		||||
    const btnSize = ref('')
 | 
			
		||||
    const toogleSize = () => {
 | 
			
		||||
      btnSize.value =  btnSize.value ? '' : 'lg'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      btnSize,
 | 
			
		||||
      toogleSize
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -253,9 +285,12 @@ export default {
 | 
			
		||||
| trigger | 触发方式 | `click` `hover` `contextMenu` |
 | 
			
		||||
| disabled | 是否禁用触发 | `true` `false` |
 | 
			
		||||
| placement | 下拉面板位置 |`top` `top-left` `top-right` `bottom` `bottom-left` `bottom-right`|
 | 
			
		||||
| autoFitPlacement| 是否自适应下拉面板位置,默认 `true` | `true` `false` | 
 | 
			
		||||
| autoFitPosition| 是否自动调整下拉面板位置,默认 `true` | `true` `false` | 
 | 
			
		||||
| autoFitWidth | 是否将下拉面板宽度设置为触发器宽度, 默认 `false` | `true` `false` |
 | 
			
		||||
| autoFitMinWidth | 是否将下拉面板最小宽度设置为触发器宽度, 默认 `true` | `true` `false` |
 | 
			
		||||
| updateAtScroll | 是否在容器滚动时更新下拉面板的位置,默认 `false` | `true` `false` |
 | 
			
		||||
| autoFixPosition | 是否在触发器或下拉面板尺寸变化时更新下拉面板位置,面板尺寸变化参见级联选择器,默认 `true` | `true` `false` |
 | 
			
		||||
| clickOutsideToClose| 是否点击外部关闭下拉面板,默认 `true`| `true` `false`|
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user