feat(design,element-plus-adapter,tdesign-vue-next-adapter): 重新实现Popover组件,不再使用element-plus或tdesign组件

This commit is contained in:
roymondchen 2024-08-15 11:44:09 +08:00 committed by roymondchen
parent cab36b49a3
commit 5e61f23106
9 changed files with 267 additions and 72 deletions

View File

@ -36,6 +36,9 @@
"vue3", "vue3",
"typescript" "typescript"
], ],
"dependencies": {
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7"
},
"devDependencies": { "devDependencies": {
"@types/node": "^18.19.0", "@types/node": "^18.19.0",
"@vitejs/plugin-vue": "^5.1.1", "@vitejs/plugin-vue": "^5.1.1",

View File

@ -1,28 +1,253 @@
<template> <template>
<component class="tmagic-design-popover" :is="uiComponent" v-bind="uiProps"> <slot name="reference"></slot>
<slot></slot> <Teleport to="body">
<div
<template #reference> v-if="popoverVisible"
<slot name="reference"></slot> class="tmagic-design-popper"
</template> tabindex="-1"
</component> ref="popperElementRef"
:class="popperClass"
:style="style"
>
<slot></slot>
<span class="tmagic-design-popper-arrow" data-popper-arrow></span>
</div>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import type { Instance } from '@popperjs/core';
import { createPopper } from '@popperjs/core';
import { getConfig } from './config'; import { useZIndex } from './index';
import type { PopoverProps } from './types'; import type { PopoverProps } from './types';
defineSlots<{
reference(props: {}): any;
default(props: {}): any;
}>();
defineOptions({ defineOptions({
name: 'TMPopover', name: 'TMPopper',
inheritAttrs: false,
}); });
const props = defineProps<PopoverProps>(); const props = withDefaults(defineProps<PopoverProps>(), {
trigger: 'hover',
disabled: false,
visible: undefined,
});
const ui = getConfig('components')?.popover; const zIndex = useZIndex();
const curZIndex = ref<number>(2);
const popoverVisible = ref(false);
const uiComponent = ui?.component || 'el-popover'; const visibleWatch = watch(
() => props.visible,
(visible) => {
console.log(visible);
if (typeof visible === 'undefined') {
nextTick(() => {
visibleWatch();
});
return;
}
const uiProps = computed(() => ui?.props(props) || props); popoverVisible.value = visible;
},
{
immediate: true,
},
);
const style = computed(() => {
if (!props.width) {
return {};
}
let { width } = props;
if (typeof width === 'number') {
width = `${width}px`;
}
return {
width,
};
});
const referenceElementRef = ref<HTMLElement>();
const popperElementRef = ref<HTMLElement>();
const instanceRef = shallowRef<Instance | undefined>();
onMounted(() => {
referenceElementRef.value = getCurrentInstance()?.proxy?.$el.nextElementSibling;
});
watch([referenceElementRef, popperElementRef], ([referenceElement, popperElement]) => {
destroy();
if (!referenceElement || !popperElement) return;
popperElement.style.zIndex = `${curZIndex.value}`;
popperElement.focus();
instanceRef.value = createPopper(referenceElement, popperElement, {
placement: props.placement || 'bottom',
strategy: 'absolute',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 10],
},
},
],
});
});
const clickHandler = () => {
if (props.disabled) return;
popoverVisible.value = !popoverVisible.value;
};
const mouseenterHandler = () => {
if (props.disabled) return;
if (timer) {
clearTimeout(timer);
}
popoverVisible.value = true;
};
let timer: NodeJS.Timeout | null = null;
const mouseleaveHandler = () => {
if (props.disabled) return;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
popoverVisible.value = false;
}, 300);
};
if (props.trigger === 'click' && typeof props.visible === 'undefined') {
watch(
referenceElementRef,
(el, prevEl) => {
el?.addEventListener('click', clickHandler);
prevEl?.removeEventListener('click', clickHandler);
},
{
immediate: true,
},
);
}
if (props.trigger === 'hover' && typeof props.visible === 'undefined') {
watch(
referenceElementRef,
(el, prevEl) => {
el?.addEventListener('mouseenter', mouseenterHandler);
prevEl?.removeEventListener('mouseenter', mouseenterHandler);
el?.addEventListener('mouseleave', mouseleaveHandler);
prevEl?.removeEventListener('mouseleave', mouseleaveHandler);
},
{
immediate: true,
},
);
watch(popperElementRef, (el, prevEl) => {
el?.addEventListener('mouseenter', mouseenterHandler);
prevEl?.removeEventListener('mouseenter', mouseenterHandler);
el?.addEventListener('mouseleave', mouseleaveHandler);
prevEl?.removeEventListener('mouseleave', mouseleaveHandler);
});
}
watch(
popoverVisible,
(popoverVisible) => {
if (!popoverVisible) {
return;
}
nextTick().then(() => {
curZIndex.value = zIndex.nextZIndex();
});
},
{
immediate: true,
},
);
const destroy = () => {
if (!instanceRef.value) return;
instanceRef.value.destroy();
instanceRef.value = undefined;
};
onBeforeUnmount(() => {
destroy();
});
</script> </script>
<style lang="scss">
.tmagic-design-popper {
min-width: 150px;
line-height: 1.4;
background-color: #fff;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
color: #606266;
border: 1px solid #e4e7ed;
border-radius: 4px;
font-size: 14px;
overflow-wrap: break-word;
box-sizing: border-box;
padding: 10px;
&:focus {
outline: none;
}
}
.tmagic-design-popper[data-popper-placement^='top'] > .tmagic-design-popper-arrow {
bottom: -4px;
}
.tmagic-design-popper[data-popper-placement^='bottom'] > .tmagic-design-popper-arrow {
top: -4px;
}
.tmagic-design-popper[data-popper-placement^='left'] > .tmagic-design-popper-arrow {
right: -4px;
}
.tmagic-design-popper[data-popper-placement^='right'] > .tmagic-design-popper-arrow {
left: -4px;
}
.tmagic-design-popper-arrow,
.tmagic-design-popper-arrow::before {
position: absolute;
width: 8px;
height: 8px;
background: inherit;
}
.tmagic-design-popper-arrow {
visibility: hidden;
}
.tmagic-design-popper-arrow::before {
visibility: visible;
content: '';
transform: rotate(45deg);
}
</style>

View File

@ -1,4 +1,5 @@
import { ComputedRef, DefineComponent, Directive, Ref } from 'vue'; import { ComputedRef, DefineComponent, Directive, Ref } from 'vue';
import type { Placement } from '@popperjs/core';
export type FieldSize = 'large' | 'default' | 'small'; export type FieldSize = 'large' | 'default' | 'small';
@ -223,15 +224,12 @@ export interface PaginationProps {
} }
export interface PopoverProps { export interface PopoverProps {
placement?: string; placement?: Placement;
width?: string | number; width?: string | number;
title?: string; trigger?: 'hover' | 'click';
trigger?: string;
effect?: string;
content?: string;
disabled?: boolean; disabled?: boolean;
popperClass?: string;
visible?: boolean; visible?: boolean;
popperClass?: string;
} }
export interface RadioProps { export interface RadioProps {
@ -570,11 +568,6 @@ export interface Components {
props: (props: PaginationProps) => PaginationProps; props: (props: PaginationProps) => PaginationProps;
}; };
popover: {
component: DefineComponent<PopoverProps, {}, any> | string;
props: (props: PopoverProps) => PopoverProps;
};
radio: { radio: {
component: DefineComponent<RadioProps, {}, any> | string; component: DefineComponent<RadioProps, {}, any> | string;
props: (props: RadioProps) => RadioProps; props: (props: RadioProps) => RadioProps;

View File

@ -81,8 +81,8 @@
} }
.page-bar-popover { .page-bar-popover {
&.el-popper.el-popover { &.tmagic-design-popper {
padding: 10px 0; padding: 4px 0;
} }
.menu-item { .menu-item {

View File

@ -43,7 +43,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@tmagic/design": "workspace:*", "@tmagic/design": "workspace:*",
"element-plus": ">=2.7.8", "element-plus": ">=2.8.0",
"typescript": "*" "typescript": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {

View File

@ -27,7 +27,6 @@ import {
ElOption, ElOption,
ElOptionGroup, ElOptionGroup,
ElPagination, ElPagination,
ElPopover,
ElRadio, ElRadio,
ElRadioButton, ElRadioButton,
ElRadioGroup, ElRadioGroup,
@ -76,7 +75,6 @@ import type {
OptionProps, OptionProps,
PaginationProps, PaginationProps,
PluginOptions, PluginOptions,
PopoverProps,
RadioButtonProps, RadioButtonProps,
RadioGroupProps, RadioGroupProps,
RadioProps, RadioProps,
@ -230,11 +228,6 @@ const adapter: PluginOptions = {
props: (props: PaginationProps) => props, props: (props: PaginationProps) => props,
}, },
popover: {
component: ElPopover as any,
props: (props: PopoverProps) => props,
},
radio: { radio: {
component: ElRadio as any, component: ElRadio as any,
props: (props: RadioProps) => props, props: (props: RadioProps) => props,

View File

@ -50,9 +50,24 @@ export interface ColumnConfig<T = any> {
table?: ColumnConfig[]; table?: ColumnConfig[];
formatter?: 'datetime' | ((item: any, row: T) => any); formatter?: 'datetime' | ((item: any, row: T) => any);
popover?: { popover?: {
placement: string; placement:
width: string; | 'auto'
trigger: string; | 'auto-start'
| 'auto-end'
| 'left'
| 'right'
| 'top'
| 'bottom'
| 'top-start'
| 'top-end'
| 'bottom-start'
| 'bottom-end'
| 'right-start'
| 'right-end'
| 'left-start'
| 'left-end';
width: string | number;
trigger: 'hover' | 'click';
tableEmbed: boolean; tableEmbed: boolean;
}; };
sortable?: boolean | 'custom'; sortable?: boolean | 'custom';

View File

@ -1,27 +0,0 @@
<template>
<TPopup
:placement="placement"
:trigger="trigger"
:disabled="disabled"
:visible="visible"
:overlayClassName="popperClass"
>
<slot name="reference"></slot>
<template #content>
<slot></slot>
</template>
</TPopup>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { Popup as TPopup, type PopupPlacement } from 'tdesign-vue-next';
import type { PopoverProps } from '@tmagic/design';
const props = defineProps<PopoverProps>();
const placement = computed(() => props.placement as PopupPlacement);
const trigger = computed(() => props.trigger as 'click' | 'focus' | 'mousedown' | 'context-menu' | 'hover');
</script>

View File

@ -64,7 +64,6 @@ import type {
OptionGroupProps, OptionGroupProps,
OptionProps, OptionProps,
PaginationProps, PaginationProps,
PopoverProps,
RadioButtonProps, RadioButtonProps,
RadioGroupProps, RadioGroupProps,
RadioProps, RadioProps,
@ -85,7 +84,6 @@ import type {
import DatePicker from './DatePicker.vue'; import DatePicker from './DatePicker.vue';
import Icon from './Icon.vue'; import Icon from './Icon.vue';
import Input from './Input.vue'; import Input from './Input.vue';
import Popover from './Popover.vue';
import Scrollbar from './Scrollbar.vue'; import Scrollbar from './Scrollbar.vue';
import TableColumn from './TableColumn.vue'; import TableColumn from './TableColumn.vue';
@ -348,11 +346,6 @@ const adapter: any = {
}), }),
}, },
popover: {
component: Popover,
props: (props: PopoverProps) => props,
},
radio: { radio: {
component: TRadio, component: TRadio,
props: (props: RadioProps) => ({ props: (props: RadioProps) => ({