This commit is contained in:
dap
2025-05-04 17:23:32 +08:00
60 changed files with 498 additions and 210 deletions

View File

@@ -38,6 +38,7 @@
},
"dependencies": {
"@vben-core/composables": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*",

View File

@@ -5,6 +5,7 @@ import type { FormSchema, MaybeComponentProps } from '../types';
import { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';
import { CircleAlert } from '@vben-core/icons';
import {
FormControl,
FormDescription,
@@ -12,6 +13,7 @@ import {
FormItem,
FormMessage,
VbenRenderContent,
VbenTooltip,
} from '@vben-core/shadcn-ui';
import { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';
@@ -354,6 +356,24 @@ onUnmounted(() => {
</template>
<!-- <slot></slot> -->
</component>
<VbenTooltip
v-if="compact && isInValid"
:delay-duration="300"
side="left"
>
<template #trigger>
<slot name="trigger">
<CircleAlert
:class="
cn(
'text-foreground/80 hover:text-foreground inline-flex size-5 cursor-pointer',
)
"
/>
</slot>
</template>
<FormMessage />
</VbenTooltip>
</slot>
</FormControl>
<!-- 自定义后缀 -->
@@ -365,7 +385,7 @@ onUnmounted(() => {
</FormDescription>
</div>
<Transition name="slide-up">
<Transition name="slide-up" v-if="!compact">
<FormMessage class="absolute bottom-1" />
</Transition>
</div>

View File

@@ -31,8 +31,8 @@ export function useVbenForm<
h(VbenUseForm, { ...props, ...attrs, formApi: extendedApi }, slots);
},
{
inheritAttrs: false,
name: 'VbenUseForm',
inheritAttrs: false,
},
);
// Add reactivity support

View File

@@ -31,6 +31,7 @@ import {
createSubMenuContext,
useMenuStyle,
} from '../hooks';
import { useMenuScroll } from '../hooks/use-menu-scroll';
import { flattedChildren } from '../utils';
import SubMenu from './sub-menu.vue';
@@ -44,6 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
mode: 'vertical',
rounded: true,
theme: 'dark',
scrollToActive: false,
});
const emit = defineEmits<{
@@ -206,15 +208,19 @@ function handleResize() {
isFirstTimeRender = false;
}
function getActivePaths() {
const activeItem = activePath.value && items.value[activePath.value];
const enableScroll = computed(
() => props.scrollToActive && props.mode === 'vertical' && !props.collapse,
);
if (!activeItem || props.mode === 'horizontal' || props.collapse) {
return [];
}
const { scrollToActiveItem } = useMenuScroll(activePath, {
enable: enableScroll,
delay: 320,
});
return activeItem.parentPaths;
}
// 监听 activePath 变化,自动滚动到激活项
watch(activePath, () => {
scrollToActiveItem();
});
// 默认展开菜单
function initMenu() {
@@ -318,6 +324,16 @@ function removeSubMenu(subMenu: MenuItemRegistered) {
function removeMenuItem(item: MenuItemRegistered) {
Reflect.deleteProperty(items.value, item.path);
}
function getActivePaths() {
const activeItem = activePath.value && items.value[activePath.value];
if (!activeItem || props.mode === 'horizontal' || props.collapse) {
return [];
}
return activeItem.parentPaths;
}
</script>
<template>
<ul

View File

@@ -0,0 +1,46 @@
import type { Ref } from 'vue';
import { watch } from 'vue';
import { useDebounceFn } from '@vueuse/core';
interface UseMenuScrollOptions {
delay?: number;
enable?: boolean | Ref<boolean>;
}
export function useMenuScroll(
activePath: Ref<string | undefined>,
options: UseMenuScrollOptions = {},
) {
const { enable = true, delay = 320 } = options;
function scrollToActiveItem() {
const isEnabled = typeof enable === 'boolean' ? enable : enable.value;
if (!isEnabled) return;
const activeElement = document.querySelector(
`aside li[role=menuitem].is-active`,
);
if (activeElement) {
activeElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
}
}
const debouncedScroll = useDebounceFn(scrollToActiveItem, delay);
watch(activePath, () => {
const isEnabled = typeof enable === 'boolean' ? enable : enable.value;
if (!isEnabled) return;
debouncedScroll();
});
return {
scrollToActiveItem,
};
}

View File

@@ -18,15 +18,9 @@ defineOptions({
const props = withDefaults(defineProps<Props>(), {
collapse: false,
// theme: 'dark',
});
const forward = useForwardProps(props);
// const emit = defineEmits<{
// 'update:openKeys': [key: Key[]];
// 'update:selectedKeys': [key: Key[]];
// }>();
</script>
<template>

View File

@@ -41,6 +41,12 @@ interface MenuProps {
*/
rounded?: boolean;
/**
* @zh_CN 是否自动滚动到激活的菜单项
* @default false
*/
scrollToActive?: boolean;
/**
* @zh_CN 菜单主题
* @default dark

View File

@@ -35,7 +35,7 @@ interface Props extends DrawerProps {
const props = withDefaults(defineProps<Props>(), {
appendToMain: false,
closeIconPlacement: 'right',
destroyOnClose: true,
destroyOnClose: false,
drawerApi: undefined,
submitting: false,
zIndex: 1000,

View File

@@ -21,9 +21,7 @@ import VbenDrawer from './drawer.vue';
const USER_DRAWER_INJECT_KEY = Symbol('VBEN_DRAWER_INJECT');
const DEFAULT_DRAWER_PROPS: Partial<DrawerProps> = {
destroyOnClose: true,
};
const DEFAULT_DRAWER_PROPS: Partial<DrawerProps> = {};
export function setDefaultDrawerProps(props: Partial<DrawerProps>) {
Object.assign(DEFAULT_DRAWER_PROPS, props);
@@ -66,9 +64,10 @@ export function useVbenDrawer<
slots,
);
},
// eslint-disable-next-line vue/one-component-per-file
{
inheritAttrs: false,
name: 'VbenParentDrawer',
inheritAttrs: false,
},
);
return [Drawer, extendedApi as ExtendedDrawerApi] as const;
@@ -107,9 +106,10 @@ export function useVbenDrawer<
return () =>
h(VbenDrawer, { ...props, ...attrs, drawerApi: extendedApi }, slots);
},
// eslint-disable-next-line vue/one-component-per-file
{
inheritAttrs: false,
name: 'VbenDrawer',
inheritAttrs: false,
},
);
injectData.extendApi?.(extendedApi);

View File

@@ -34,7 +34,7 @@ interface Props extends ModalProps {
const props = withDefaults(defineProps<Props>(), {
appendToMain: false,
destroyOnClose: true,
destroyOnClose: false,
modalApi: undefined,
});

View File

@@ -17,9 +17,7 @@ import VbenModal from './modal.vue';
const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT');
const DEFAULT_MODAL_PROPS: Partial<ModalProps> = {
destroyOnClose: true,
};
const DEFAULT_MODAL_PROPS: Partial<ModalProps> = {};
export function setDefaultModalProps(props: Partial<ModalProps>) {
Object.assign(DEFAULT_MODAL_PROPS, props);
@@ -65,9 +63,10 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
slots,
);
},
// eslint-disable-next-line vue/one-component-per-file
{
inheritAttrs: false,
name: 'VbenParentModal',
inheritAttrs: false,
},
);
return [Modal, extendedApi as ExtendedModalApi] as const;
@@ -114,9 +113,10 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
slots,
);
},
// eslint-disable-next-line vue/one-component-per-file
{
inheritAttrs: false,
name: 'VbenModal',
inheritAttrs: false,
},
);
injectData.extendApi?.(extendedApi);

View File

@@ -21,6 +21,7 @@ interface Props extends PopoverRootProps {
class?: ClassType;
contentClass?: ClassType;
contentProps?: PopoverContentProps;
triggerClass?: ClassType;
}
const props = withDefaults(defineProps<Props>(), {});
@@ -32,6 +33,7 @@ const delegatedProps = computed(() => {
class: _cls,
contentClass: _,
contentProps: _cProps,
triggerClass: _tClass,
...delegated
} = props;
@@ -43,7 +45,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
<template>
<PopoverRoot v-bind="forwarded">
<PopoverTrigger>
<PopoverTrigger :class="triggerClass">
<slot name="trigger"></slot>
<PopoverContent

View File

@@ -12,7 +12,6 @@ interface Props extends TabsProps {}
defineOptions({
name: 'VbenTabsChrome',
// eslint-disable-next-line perfectionist/sort-objects
inheritAttrs: false,
});

View File

@@ -12,7 +12,7 @@ interface Props extends TabsProps {}
defineOptions({
name: 'VbenTabs',
// eslint-disable-next-line perfectionist/sort-objects
inheritAttrs: false,
});
const props = withDefaults(defineProps<Props>(), {