♻️(component): [dropdown]移除包装元素
This commit is contained in:
parent
8d2acb70e1
commit
f901c8b07f
@ -21,6 +21,7 @@
|
||||
}
|
||||
|
||||
.layui-cascader {
|
||||
display: inline-block;
|
||||
&[size="lg"] {
|
||||
.set-size(@lg,@lg-width);
|
||||
}
|
||||
|
@ -1,62 +1,64 @@
|
||||
<template>
|
||||
<lay-dropdown
|
||||
class="layui-cascader"
|
||||
:class="{ 'layui-cascader-opend': openState }"
|
||||
ref="dropdownRef"
|
||||
:autoFitMinWidth="false"
|
||||
:updateAtScroll="true"
|
||||
:disabled="dropDownDisabled"
|
||||
<div
|
||||
:size="size"
|
||||
@open="openState = true"
|
||||
@hide="openState = false"
|
||||
:class="['layui-cascader', { 'layui-cascader-opend': openState }]"
|
||||
>
|
||||
<lay-input
|
||||
v-model="displayValue"
|
||||
readonly
|
||||
suffix-icon="layui-icon-triangle-d"
|
||||
:placeholder="placeholder"
|
||||
v-if="!slots.default"
|
||||
:allow-clear="allowClear"
|
||||
@clear="onClear"
|
||||
:size="size"
|
||||
></lay-input>
|
||||
<slot v-else></slot>
|
||||
<lay-dropdown
|
||||
ref="dropdownRef"
|
||||
:autoFitMinWidth="false"
|
||||
:updateAtScroll="true"
|
||||
:disabled="dropDownDisabled"
|
||||
@show="openState = true"
|
||||
@hide="openState = false"
|
||||
>
|
||||
<lay-input
|
||||
v-model="displayValue"
|
||||
readonly
|
||||
suffix-icon="layui-icon-triangle-d"
|
||||
:placeholder="placeholder"
|
||||
v-if="!slots.default"
|
||||
:allow-clear="allowClear"
|
||||
@clear="onClear"
|
||||
:size="size"
|
||||
></lay-input>
|
||||
<slot v-else></slot>
|
||||
|
||||
<template #content>
|
||||
<div class="layui-cascader-panel">
|
||||
<template v-for="(itemCol, index) in treeData">
|
||||
<lay-scroll
|
||||
height="180px"
|
||||
class="layui-cascader-menu"
|
||||
:key="'cascader-menu' + index"
|
||||
v-if="itemCol.data.length"
|
||||
>
|
||||
<div
|
||||
class="layui-cascader-menu-item"
|
||||
v-for="(item, i) in itemCol.data"
|
||||
:key="index + i"
|
||||
@click="selectBar(item, i, index)"
|
||||
:class="[
|
||||
{
|
||||
'layui-cascader-selected': itemCol.selectIndex === i,
|
||||
},
|
||||
]"
|
||||
<template #content>
|
||||
<div class="layui-cascader-panel">
|
||||
<template v-for="(itemCol, index) in treeData">
|
||||
<lay-scroll
|
||||
height="180px"
|
||||
class="layui-cascader-menu"
|
||||
:key="'cascader-menu' + index"
|
||||
v-if="itemCol.data.length"
|
||||
>
|
||||
<slot
|
||||
:name="item.slot"
|
||||
v-if="item.slot && slots[item.slot]"
|
||||
></slot>
|
||||
<template v-else>{{ item.label }}</template>
|
||||
<i
|
||||
class="layui-icon layui-icon-right"
|
||||
v-if="item.children && item.children.length"
|
||||
></i>
|
||||
</div>
|
||||
</lay-scroll>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
<div
|
||||
class="layui-cascader-menu-item"
|
||||
v-for="(item, i) in itemCol.data"
|
||||
:key="index + i"
|
||||
@click="selectBar(item, i, index)"
|
||||
:class="[
|
||||
{
|
||||
'layui-cascader-selected': itemCol.selectIndex === i,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<slot
|
||||
:name="item.slot"
|
||||
v-if="item.slot && slots[item.slot]"
|
||||
></slot>
|
||||
<template v-else>{{ item.label }}</template>
|
||||
<i
|
||||
class="layui-icon layui-icon-right"
|
||||
v-if="item.children && item.children.length"
|
||||
></i>
|
||||
</div>
|
||||
</lay-scroll>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
:class="['layui-date-picker', { 'layui-date-range-picker': range }]"
|
||||
:size="size"
|
||||
>
|
||||
<lay-dropdown
|
||||
ref="dropdownRef"
|
||||
:disabled="disabled"
|
||||
:autoFitMinWidth="false"
|
||||
updateAtScroll
|
||||
class="layui-date-picker"
|
||||
:class="{ 'layui-date-range-picker': range }"
|
||||
:size="size"
|
||||
>
|
||||
<lay-input
|
||||
:name="name"
|
||||
|
@ -1,12 +1,25 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayDropdown",
|
||||
inheritAttrs: false,
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
import { ComputedRef, CSSProperties, inject, reactive, Ref, toRefs } from "vue";
|
||||
import {
|
||||
ComputedRef,
|
||||
CSSProperties,
|
||||
h,
|
||||
inject,
|
||||
reactive,
|
||||
Ref,
|
||||
toRefs,
|
||||
useSlots,
|
||||
Fragment,
|
||||
cloneVNode,
|
||||
useAttrs,
|
||||
} from "vue";
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
@ -30,6 +43,8 @@ import {
|
||||
DropdownContext,
|
||||
} from "./interface";
|
||||
import TeleportWrapper from "../_components/teleportWrapper.vue";
|
||||
import { useFirstElement } from "./useFirstElement";
|
||||
import RenderFunction from "../_components/renderFunction";
|
||||
|
||||
export type DropdownTrigger = "click" | "hover" | "focus" | "contextMenu";
|
||||
|
||||
@ -75,13 +90,15 @@ const props = withDefaults(defineProps<LayDropdownProps>(), {
|
||||
alignPoint: false,
|
||||
popupContainer: "body",
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
const attrs = useAttrs();
|
||||
const childrenRefs = new Set<Ref<HTMLElement>>();
|
||||
const dropdownCtx = inject<DropdownContext | undefined>(
|
||||
dropdownInjectionKey,
|
||||
undefined
|
||||
);
|
||||
const dropdownRef = shallowRef<HTMLElement | undefined>();
|
||||
const { children, firstElement: dropdownRef } = useFirstElement();
|
||||
//const dropdownRef = shallowRef<HTMLElement | undefined>();
|
||||
const contentRef = shallowRef<HTMLElement | undefined>();
|
||||
const contentStyle = ref<CSSProperties>({});
|
||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||
@ -91,6 +108,7 @@ const mousePosition = reactive({
|
||||
});
|
||||
const { x: mouseLeft, y: mouseTop } = toRefs(mousePosition);
|
||||
const openState = ref(false);
|
||||
let scrollElements: HTMLElement[] | undefined;
|
||||
|
||||
const containerRef = computed(() =>
|
||||
props.popupContainer
|
||||
@ -564,7 +582,28 @@ onClickOutside(
|
||||
}
|
||||
);
|
||||
|
||||
let scrollElements: HTMLElement[] | undefined;
|
||||
const onlyChildRenderFunc = () => {
|
||||
const slotContent = slots.default ? slots.default() : [];
|
||||
const transformedSlotContent = slotContent.map((vnode) =>
|
||||
cloneVNode(
|
||||
vnode,
|
||||
{
|
||||
onClick: handleClick,
|
||||
onContextmenu: handleContextMenuClick,
|
||||
onMouseenter: handleMouseEnter,
|
||||
onMouseleave: handleMouseLeave,
|
||||
onFocusin: handleFocusin,
|
||||
onFocusout: handleFocusout,
|
||||
...attrs,
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
children.value = transformedSlotContent;
|
||||
return h(Fragment, children.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.updateAtScroll) {
|
||||
scrollElements = getScrollElements(dropdownRef.value);
|
||||
@ -611,29 +650,20 @@ defineExpose({ show, hide, toggle });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="dropdownRef"
|
||||
class="layui-dropdown"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
@focusin="handleFocusin()"
|
||||
@focusout="handleFocusout()"
|
||||
:class="{ 'layui-dropdown-up': openState }"
|
||||
>
|
||||
<div @click="handleClick" @contextmenu="handleContextMenuClick">
|
||||
<slot></slot>
|
||||
<RenderFunction
|
||||
:renderFunc="onlyChildRenderFunc"
|
||||
v-bind="$attrs"
|
||||
></RenderFunction>
|
||||
<TeleportWrapper :to="popupContainer">
|
||||
<div
|
||||
v-if="openState"
|
||||
ref="contentRef"
|
||||
class="layui-dropdown-content layui-anim layui-anim-upbit"
|
||||
:style="contentStyle"
|
||||
@mouseenter="handleMouseEnterWithContext"
|
||||
@mouseleave="handleMouseLeaveWithContext"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
<TeleportWrapper :to="popupContainer">
|
||||
<dl
|
||||
v-if="openState"
|
||||
ref="contentRef"
|
||||
class="layui-dropdown-content layui-anim layui-anim-upbit"
|
||||
:style="contentStyle"
|
||||
@mouseenter="handleMouseEnterWithContext"
|
||||
@mouseleave="handleMouseLeaveWithContext"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
</dl>
|
||||
</TeleportWrapper>
|
||||
</div>
|
||||
</TeleportWrapper>
|
||||
</template>
|
||||
|
103
package/component/src/component/dropdown/useFirstElement.ts
Normal file
103
package/component/src/component/dropdown/useFirstElement.ts
Normal file
@ -0,0 +1,103 @@
|
||||
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,
|
||||
};
|
||||
};
|
@ -478,7 +478,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<lay-space>
|
||||
<lay-dropdown placement="bottom-left" autoFitWidth updateAtScroll>
|
||||
<lay-dropdown placement="bottom-start" autoFitWidth updateAtScroll>
|
||||
<lay-button type="primary">autoFitWidth</lay-button>
|
||||
<template #content>
|
||||
<lay-dropdown-menu>
|
||||
@ -488,7 +488,7 @@ export default {
|
||||
</lay-dropdown-menu>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
<lay-dropdown placement="bottom-left" :autoFitMinWidth="false" updateAtScroll>
|
||||
<lay-dropdown placement="bottom-start" :autoFitMinWidth="false" updateAtScroll>
|
||||
<lay-button type="primary">关闭 autoFitMinWidth</lay-button>
|
||||
<template #content>
|
||||
<lay-dropdown-menu>
|
||||
@ -498,7 +498,7 @@ export default {
|
||||
</lay-dropdown-menu>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
<lay-dropdown placement="bottom-left" updateAtScroll>
|
||||
<lay-dropdown placement="bottom-start" updateAtScroll>
|
||||
<lay-button type="primary">updateAtScroll</lay-button>
|
||||
<template #content>
|
||||
<lay-dropdown-menu>
|
||||
@ -508,7 +508,7 @@ export default {
|
||||
</lay-dropdown-menu>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
<lay-dropdown placement="bottom-left" updateAtScroll :contentOffset="8">
|
||||
<lay-dropdown placement="bottom-start" updateAtScroll :contentOffset="8">
|
||||
<lay-button type="primary">contentOffset: 8px</lay-button>
|
||||
<template #content>
|
||||
<lay-dropdown-menu>
|
||||
|
Loading…
x
Reference in New Issue
Block a user