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