Browse Source

perf: optimization of tabbar display (#4169)

* perf: optimization of tabbar display

* fix: ci error

* chore: typo

* chore: typo
pull/4171/head
Vben 1 month ago
committed by GitHub
parent
commit
0faf7810b6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      apps/web-antd/package.json
  2. 4
      apps/web-ele/package.json
  3. 4
      apps/web-naive/package.json
  4. 2
      docs/package.json
  5. 3
      internal/lint-configs/eslint-config/src/configs/typescript.ts
  6. 2
      package.json
  7. 4
      packages/@core/base/icons/package.json
  8. 2
      packages/@core/base/shared/package.json
  9. 2
      packages/@core/base/typings/package.json
  10. 35
      packages/@core/base/typings/src/helper.d.ts
  11. 4
      packages/@core/composables/package.json
  12. 18
      packages/@core/composables/src/use-content-style.ts
  13. 2
      packages/@core/composables/src/use-sortable.test.ts
  14. 2
      packages/@core/composables/src/use-sortable.ts
  15. 4
      packages/@core/preferences/package.json
  16. 4
      packages/@core/ui-kit/layout-ui/package.json
  17. 4
      packages/@core/ui-kit/menu-ui/package.json
  18. 6
      packages/@core/ui-kit/shadcn-ui/package.json
  19. 76
      packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue
  20. 3
      packages/@core/ui-kit/tabs-ui/package.json
  21. 264
      packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue
  22. 162
      packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue
  23. 170
      packages/@core/ui-kit/tabs-ui/src/tabs-view.vue
  24. 13
      packages/@core/ui-kit/tabs-ui/src/types.ts
  25. 110
      packages/@core/ui-kit/tabs-ui/src/use-tabs-drag.ts
  26. 160
      packages/@core/ui-kit/tabs-ui/src/use-tabs-view-scroll.ts
  27. 2
      packages/effects/access/package.json
  28. 4
      packages/effects/chart-ui/package.json
  29. 4
      packages/effects/common-ui/package.json
  30. 2
      packages/effects/hooks/package.json
  31. 4
      packages/effects/layouts/package.json
  32. 2
      packages/locales/package.json
  33. 2
      packages/stores/package.json
  34. 17
      packages/stores/src/modules/tabbar.ts
  35. 2
      packages/types/package.json
  36. 4
      playground/package.json
  37. 2
      playground/src/router/routes/modules/demos.ts
  38. 115
      pnpm-lock.yaml

4
apps/web-antd/package.json

@ -40,11 +40,11 @@
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"ant-design-vue": "^4.2.3",
"dayjs": "^1.11.12",
"pinia": "2.2.2",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
}
}

4
apps/web-ele/package.json

@ -40,11 +40,11 @@
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"dayjs": "^1.11.12",
"element-plus": "^2.8.0",
"pinia": "2.2.2",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
},
"devDependencies": {

4
apps/web-naive/package.json

@ -40,10 +40,10 @@
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"naive-ui": "^2.39.0",
"pinia": "2.2.2",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
}
}

2
docs/package.json

@ -14,6 +14,6 @@
"@nolebase/vitepress-plugin-git-changelog": "^2.4.0",
"@vite-pwa/vitepress": "^0.5.0",
"vitepress": "^1.3.2",
"vue": "^3.4.38"
"vue": "^3.4.37"
}
}

3
internal/lint-configs/eslint-config/src/configs/typescript.ts

@ -42,7 +42,8 @@ export async function typescript(): Promise<Linter.Config[]> {
},
],
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
// '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': [

2
package.json

@ -94,7 +94,7 @@
"node": ">=20",
"pnpm": ">=9"
},
"packageManager": "pnpm@9.7.0",
"packageManager": "pnpm@9.7.1",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {

4
packages/@core/base/icons/package.json

@ -35,7 +35,7 @@
},
"dependencies": {
"@iconify/vue": "^4.1.2",
"lucide-vue-next": "^0.427.0",
"vue": "^3.4.38"
"lucide-vue-next": "^0.428.0",
"vue": "^3.4.37"
}
}

2
packages/@core/base/shared/package.json

@ -56,7 +56,7 @@
},
"dependencies": {
"@ctrl/tinycolor": "^4.1.0",
"@vue/shared": "^3.4.38",
"@vue/shared": "^3.4.37",
"clsx": "^2.1.1",
"defu": "^6.1.4",
"lodash.clonedeep": "^4.5.0",

2
packages/@core/base/typings/package.json

@ -38,7 +38,7 @@
}
},
"dependencies": {
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
}
}

35
packages/@core/base/typings/src/helper.d.ts

@ -107,20 +107,23 @@ type MergeAll<
? MergeAll<Rest, Merge<R, F>>
: R;
export {
type AnyFunction,
type AnyNormalFunction,
type AnyPromiseFunction,
type DeepPartial,
type DeepReadonly,
type IntervalHandle,
type MaybeComputedRef,
type MaybeReadonlyRef,
type Merge,
type MergeAll,
type NonNullable,
type Nullable,
type ReadonlyRecordable,
type Recordable,
type TimeoutHandle,
type EmitType = (name: Name, ...args: any[]) => void;
export type {
AnyFunction,
AnyNormalFunction,
AnyPromiseFunction,
DeepPartial,
DeepReadonly,
EmitType,
IntervalHandle,
MaybeComputedRef,
MaybeReadonlyRef,
Merge,
MergeAll,
NonNullable,
Nullable,
ReadonlyRecordable,
Recordable,
TimeoutHandle,
};

4
packages/@core/composables/package.json

@ -36,10 +36,10 @@
},
"dependencies": {
"@vben-core/shared": "workspace:*",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"radix-vue": "^1.9.4",
"sortablejs": "^1.15.2",
"vue": "^3.4.38"
"vue": "^3.4.37"
},
"devDependencies": {
"@types/sortablejs": "^1.15.8"

18
packages/@core/composables/src/use-content-style.ts

@ -1,5 +1,5 @@
import type { CSSProperties } from 'vue';
import { computed, nextTick, onMounted, ref } from 'vue';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import {
CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT,
@ -14,6 +14,7 @@ import { useCssVar, useDebounceFn } from '@vueuse/core';
* @zh_CN content style
*/
function useContentStyle() {
let resizeObserver: null | ResizeObserver = null;
const contentElement = ref<HTMLDivElement | null>(null);
const visibleDomRect = ref<null | VisibleDomRect>(null);
const contentHeight = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);
@ -41,12 +42,15 @@ function useContentStyle() {
);
onMounted(() => {
nextTick(() => {
if (contentElement.value) {
const observer = new ResizeObserver(debouncedCalcHeight);
observer.observe(contentElement.value);
}
});
if (contentElement.value && !resizeObserver) {
resizeObserver = new ResizeObserver(debouncedCalcHeight);
resizeObserver.observe(contentElement.value);
}
});
onUnmounted(() => {
resizeObserver?.disconnect();
resizeObserver = null;
});
return { contentElement, overlayStyle, visibleDomRect };

2
packages/@core/composables/src/use-sortable.test.ts

@ -39,7 +39,7 @@ describe('useSortable', () => {
expect(Sortable.default.create).toHaveBeenCalledWith(
mockElement,
expect.objectContaining({
animation: 100,
animation: 300,
delay: 400,
delayOnTouchOnly: true,
...customOptions,

2
packages/@core/composables/src/use-sortable.ts

@ -18,7 +18,7 @@ function useSortable<T extends HTMLElement>(
// Sortable?.default?.mount?.(AutoScroll);
const sortable = Sortable?.default?.create?.(sortableContainer, {
animation: 100,
animation: 300,
delay: 400,
delayOnTouchOnly: true,
...options,

4
packages/@core/preferences/package.json

@ -31,7 +31,7 @@
"dependencies": {
"@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.1",
"vue": "^3.4.38"
"@vueuse/core": "^11.0.0",
"vue": "^3.4.37"
}
}

4
packages/@core/ui-kit/layout-ui/package.json

@ -41,7 +41,7 @@
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.1",
"vue": "^3.4.38"
"@vueuse/core": "^11.0.0",
"vue": "^3.4.37"
}
}

4
packages/@core/ui-kit/menu-ui/package.json

@ -42,7 +42,7 @@
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.1",
"vue": "^3.4.38"
"@vueuse/core": "^11.0.0",
"vue": "^3.4.37"
}
}

6
packages/@core/ui-kit/shadcn-ui/package.json

@ -46,10 +46,10 @@
"@vben-core/icons": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"class-variance-authority": "^0.7.0",
"lucide-vue-next": "^0.427.0",
"lucide-vue-next": "^0.428.0",
"radix-vue": "^1.9.4",
"vue": "^3.4.38"
"vue": "^3.4.37"
}
}

76
packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { cn } from '@vben-core/shared';
@ -11,6 +11,10 @@ interface Props {
scrollBarClass?: any;
shadow?: boolean;
shadowBorder?: boolean;
shadowBottom?: boolean;
shadowLeft?: boolean;
shadowRight?: boolean;
shadowTop?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
@ -18,29 +22,66 @@ const props = withDefaults(defineProps<Props>(), {
horizontal: false,
shadow: false,
shadowBorder: false,
shadowBottom: true,
shadowLeft: false,
shadowRight: false,
shadowTop: true,
});
const emit = defineEmits<{
scrollAt: [{ bottom: boolean; left: boolean; right: boolean; top: boolean }];
}>();
const isAtTop = ref(true);
const isAtRight = ref(false);
const isAtBottom = ref(false);
const isAtLeft = ref(true);
const showShadowTop = computed(() => props.shadow && props.shadowTop);
const showShadowBottom = computed(() => props.shadow && props.shadowBottom);
const showShadowLeft = computed(() => props.shadow && props.shadowLeft);
const showShadowRight = computed(() => props.shadow && props.shadowRight);
const computedShadowClasses = computed(() => ({
'shadow-both':
!isAtLeft.value &&
!isAtRight.value &&
showShadowLeft.value &&
showShadowRight.value,
'shadow-left': !isAtLeft.value && showShadowLeft.value,
'shadow-right': !isAtRight.value && showShadowRight.value,
}));
function handleScroll(event: Event) {
const target = event.target as HTMLElement;
const scrollTop = target?.scrollTop ?? 0;
const scrollLeft = target?.scrollLeft ?? 0;
const offsetHeight = target?.offsetHeight ?? 0;
const offsetWidth = target?.offsetWidth ?? 0;
const scrollHeight = target?.scrollHeight ?? 0;
const scrollWidth = target?.scrollWidth ?? 0;
isAtTop.value = scrollTop <= 0;
isAtLeft.value = scrollLeft <= 0;
isAtBottom.value = scrollTop + offsetHeight >= scrollHeight;
isAtRight.value = scrollLeft + offsetWidth >= scrollWidth;
emit('scrollAt', {
bottom: isAtBottom.value,
left: isAtLeft.value,
right: isAtRight.value,
top: isAtTop.value,
});
}
</script>
<template>
<ScrollArea
:class="[cn(props.class)]"
:class="[cn(props.class), computedShadowClasses]"
:on-scroll="handleScroll"
class="relative"
class="vben-scrollbar relative"
>
<div
v-if="shadow"
v-if="showShadowTop"
:class="{
'opacity-100': !isAtTop,
'border-border border-t': shadowBorder && !isAtTop,
@ -49,7 +90,7 @@ function handleScroll(event: Event) {
></div>
<slot></slot>
<div
v-if="shadow"
v-if="showShadowBottom"
:class="{
'opacity-100': !isAtTop && !isAtBottom,
'border-border border-b': shadowBorder && !isAtTop && !isAtBottom,
@ -65,6 +106,31 @@ function handleScroll(event: Event) {
</template>
<style scoped>
.vben-scrollbar {
&:not(.shadow-both).shadow-left {
mask-image: linear-gradient(90deg, transparent, #000 16px);
}
&:not(.shadow-both).shadow-right {
mask-image: linear-gradient(
90deg,
#000 0%,
#000 calc(100% - 16px),
transparent
);
}
&.shadow-both {
mask-image: linear-gradient(
90deg,
transparent,
#000 16px,
#000 calc(100% - 16px),
transparent 100%
);
}
}
.scrollbar-top-shadow {
background: linear-gradient(
to bottom,

3
packages/@core/ui-kit/tabs-ui/package.json

@ -41,6 +41,7 @@
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/typings": "workspace:*",
"vue": "^3.4.38"
"@vueuse/core": "^11.0.0",
"vue": "^3.4.37"
}
}

264
packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue

@ -3,10 +3,10 @@ import type { TabDefinition } from '@vben-core/typings';
import type { TabConfig, TabsProps } from '../../types';
import { computed, ref, watch } from 'vue';
import { computed, ref } from 'vue';
import { MdiPin, X } from '@vben-core/icons';
import { VbenContextMenu, VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
interface Props extends TabsProps {}
@ -20,17 +20,17 @@ const props = withDefaults(defineProps<Props>(), {
contentClass: 'vben-tabs-content',
contextMenus: () => [],
gap: 7,
maxWidth: 150,
minWidth: 80,
tabs: () => [],
});
const emit = defineEmits<{ close: [string]; unpin: [TabDefinition] }>();
const emit = defineEmits<{
close: [string];
unpin: [TabDefinition];
}>();
const active = defineModel<string>('active');
const contentRef = ref();
const tabRef = ref();
const tabWidth = ref<number>(props.maxWidth);
const style = computed(() => {
const { gap } = props;
@ -53,148 +53,118 @@ const tabsView = computed((): TabConfig[] => {
};
});
});
watch(active, () => {
scrollIntoView();
});
function scrollIntoView() {
setTimeout(() => {
const element = document.querySelector(`.tabs-chrome__item.is-active`);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
}
</script>
<template>
<div :style="style" class="tabs-chrome size-full flex-1 overflow-hidden pt-1">
<VbenScrollbar
id="tabs-scrollbar"
class="tabs-chrome__scrollbar h-full"
horizontal
scroll-bar-class="z-10 hidden"
>
<!-- footer -> 4px -->
<div
ref="contentRef"
:class="contentClass"
:style="style"
class="tabs-chrome !flex h-full w-max pr-6"
>
<TransitionGroup name="slide-left">
<div
ref="contentRef"
:class="contentClass"
class="relative !flex h-full w-max"
v-for="(tab, i) in tabsView"
:key="tab.key"
ref="tabRef"
:class="[{ 'is-active': tab.key === active, dragable: !tab.affixTab }]"
:data-active-tab="active"
:data-index="i"
class="tabs-chrome__item draggable group relative -mr-3 flex h-full select-none items-center"
data-tab-item="true"
@click="active = tab.key"
>
<TransitionGroup name="slide-left">
<div
v-for="(tab, i) in tabsView"
:key="tab.key"
ref="tabRef"
:class="[
{ 'is-active': tab.key === active, dragable: !tab.affixTab },
]"
:data-active-tab="active"
:data-index="i"
:style="{
width: `${tabWidth}px`,
left: `${(tabWidth - gap * 2) * i}px`,
}"
class="tabs-chrome__item group absolute flex h-full select-none items-center transition-all"
@click="active = tab.key"
>
<VbenContextMenu
:handler-data="tab"
:menus="contextMenus"
:modal="false"
item-class="pr-6"
<VbenContextMenu
:handler-data="tab"
:menus="contextMenus"
:modal="false"
item-class="pr-6"
>
<div class="relative size-full px-1">
<!-- divider -->
<div
v-if="i !== 0 && tab.key !== active"
class="tabs-chrome__divider bg-foreground/50 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
></div>
<!-- background -->
<div
class="tabs-chrome__background absolute z-[-1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
>
<div class="size-full">
<!-- divider -->
<div
v-if="i !== 0 && tab.key !== active"
class="tabs-chrome__divider bg-foreground/60 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
></div>
<!-- background -->
<div
class="tabs-chrome__background absolute z-[1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
>
<div
class="tabs-chrome__background-content group-[.is-active]:bg-primary/15 dark:group-[.is-active]:bg-accent h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"
></div>
<svg
class="tabs-chrome__background-before group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 left-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
</svg>
<svg
class="tabs-chrome__background-after group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 right-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
</svg>
</div>
<!-- extra -->
<div
class="tabs-chrome__extra absolute right-[calc(var(--gap)*1.5)] top-1/2 z-[3] size-4 translate-y-[-50%]"
>
<!-- close-icon -->
<X
v-show="
!tab.affixTab && tabsView.length > 1 && tab.closable
"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('close', tab.key)"
/>
<MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('unpin', tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="tabs-chrome__item-main group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground text-accent-foreground absolute left-0 right-0 z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-4 duration-150 group-hover:pr-3"
>
<VbenIcon
v-if="showIcon"
:icon="tab.icon"
class="ml-[var(--gap)] flex size-4 items-center overflow-hidden"
fallback
/>
<span
class="tabs-chrome__label ml-[var(--gap)] flex-1 overflow-hidden whitespace-nowrap text-sm"
>
{{ tab.title }}
</span>
</div>
</div>
</VbenContextMenu>
<div
class="tabs-chrome__background-content group-[.is-active]:bg-heavy dark:group-[.is-active]:bg-accent h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"
></div>
<svg
class="tabs-chrome__background-before group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 left-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
</svg>
<svg
class="tabs-chrome__background-after group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 right-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
</svg>
</div>
<!-- extra -->
<div
class="tabs-chrome__extra absolute right-[var(--gap)] top-1/2 z-[3] size-4 translate-y-[-50%]"
>
<!-- close-icon -->
<X
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[2px] size-3 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('close', tab.key)"
/>
<MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:text-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('unpin', tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="tabs-chrome__item-main group-[.is-active]:text-accent-foreground dark:group-[.is-active]:text-accent-foreground text-accent-foreground z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pl-2 pr-4 duration-150"
>
<VbenIcon
v-if="showIcon"
:icon="tab.icon"
class="mr-1 flex size-4 items-center overflow-hidden"
fallback
/>
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
{{ tab.title }}
</span>
</div>
</div>
</TransitionGroup>
</VbenContextMenu>
</div>
<!-- footer -->
<!-- <div class="bg-background h-1"></div> -->
</VbenScrollbar>
</TransitionGroup>
</div>
</template>
<style scoped>
.tabs-chrome {
.dragging {
.tabs-chrome__item-main {
/* .dragging { */
/* .tabs-chrome__item-main {
@apply pr-0;
}
} */
.tabs-chrome__extra {
/* .tabs-chrome__extra {
@apply hidden;
}
}
} */
/* } */
&__item:not(.dragging) {
@apply cursor-pointer;
&__item {
&:hover:not(.is-active) {
& + .tabs-chrome__item {
.tabs-chrome__divider {
@ -207,13 +177,10 @@ function scrollIntoView() {
}
.tabs-chrome__background {
&-content {
@apply bg-accent mx-1 rounded-md pb-2;
}
@apply pb-[2px];
&-before,
&-after {
@apply fill-primary/0;
&-content {
@apply bg-accent-hover mx-[2px] rounded-md;
}
}
}
@ -226,30 +193,7 @@ function scrollIntoView() {
@apply opacity-0 !important;
}
}
.tabs-chrome__background {
@apply opacity-100;
/* &-content {
@apply bg-accent;
}
&-before,
&-after {
@apply fill-heavy;
} */
}
}
}
&__scrollbar,
&__label {
mask-image: linear-gradient(
90deg,
#000 0%,
#000 calc(100% - 16px),
transparent
);
}
}
</style>

162
packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue

@ -3,10 +3,10 @@ import type { TabDefinition } from '@vben-core/typings';
import type { TabConfig, TabsProps } from '../../types';
import { computed, watch } from 'vue';
import { computed } from 'vue';
import { MdiPin, X } from '@vben-core/icons';
import { VbenContextMenu, VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
interface Props extends TabsProps {}
@ -21,7 +21,10 @@ const props = withDefaults(defineProps<Props>(), {
tabs: () => [],
});
const emit = defineEmits<{ close: [string]; unpin: [TabDefinition] }>();
const emit = defineEmits<{
close: [string];
unpin: [TabDefinition];
}>();
const active = defineModel<string>('active');
const typeWithClass = computed(() => {
@ -55,108 +58,71 @@ const tabsView = computed((): TabConfig[] => {
};
});
});
watch(active, () => {
scrollIntoView();
});
function scrollIntoView() {
setTimeout(() => {
const element = document.querySelector(`.tabs-chrome__item.is-active`);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
}
</script>
<template>
<div class="size-full flex-1 overflow-hidden">
<VbenScrollbar
id="tabs-scrollbar"
class="tabs-scrollbar h-full"
horizontal
scroll-bar-class="z-10 hidden"
>
<div
:class="contentClass"
class="relative !flex h-full w-max items-center pr-6"
>
<TransitionGroup name="slide-left">
<div
:class="contentClass"
class="relative !flex h-full w-max items-center"
v-for="(tab, i) in tabsView"
:key="tab.key"
:class="[
{
'is-active dark:bg-accent bg-primary/15': tab.key === active,
dragable: !tab.affixTab,
},
typeWithClass.content,
]"
:data-index="i"
class="tab-item [&:not(.is-active)]:hover:bg-accent group relative flex cursor-pointer select-none"
data-tab-item="true"
@click="active = tab.key"
>
<TransitionGroup name="slide-left">
<div
v-for="(tab, i) in tabsView"
:key="tab.key"
:class="[
{
'is-active dark:bg-accent bg-primary/15': tab.key === active,
dragable: !tab.affixTab,
},
typeWithClass.content,
]"
:data-index="i"
class="tabs-chrome__item [&:not(.is-active)]:hover:bg-accent group relative flex cursor-pointer select-none transition-all duration-300"
@click="active = tab.key"
>
<VbenContextMenu
:handler-data="tab"
:menus="contextMenus"
:modal="false"
item-class="pr-6"
<VbenContextMenu
:handler-data="tab"
:menus="contextMenus"
:modal="false"
item-class="pr-6"
>
<div class="relative flex size-full items-center">
<!-- extra -->
<div
class="absolute right-1.5 top-1/2 z-[3] translate-y-[-50%] overflow-hidden"
>
<div class="relative flex size-full items-center">
<!-- extra -->
<div
class="absolute right-1.5 top-1/2 z-[3] translate-y-[-50%] overflow-hidden"
>
<!-- close-icon -->
<X
v-show="
!tab.affixTab && tabsView.length > 1 && tab.closable
"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('close', tab.key)"
/>
<MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('unpin', tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="text-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
>
<VbenIcon
v-if="showIcon"
:icon="tab.icon"
class="mr-2 flex size-4 items-center overflow-hidden"
fallback
/>
<span
class="flex-1 overflow-hidden whitespace-nowrap text-sm"
>
{{ tab.title }}
</span>
</div>
</div>
</VbenContextMenu>
<!-- close-icon -->
<X
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('close', tab.key)"
/>
<MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('unpin', tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="text-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
>
<VbenIcon
v-if="showIcon"
:icon="tab.icon"
class="mr-2 flex size-4 items-center overflow-hidden"
fallback
/>
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
{{ tab.title }}
</span>
</div>
</div>
</TransitionGroup>
</VbenContextMenu>
</div>
</VbenScrollbar>
</TransitionGroup>
</div>
</template>
<style scoped>
.tabs-scrollbar {
mask-image: linear-gradient(
90deg,
#000 0%,
#000 calc(100% - 16px),
transparent
);
}
</style>

170
packages/@core/ui-kit/tabs-ui/src/tabs-view.vue

@ -1,15 +1,12 @@
<script setup lang="ts">
import type { Sortable } from '@vben-core/composables';
import type { TabDefinition } from '@vben-core/typings';
import type { TabsEmits, TabsProps } from './types';
import type { TabsProps } from './types';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { useForwardPropsEmits, useSortable } from '@vben-core/composables';
import { useForwardPropsEmits } from '@vben-core/composables';
import { ChevronLeft, ChevronRight } from '@vben-core/icons';
import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { Tabs, TabsChrome } from './components';
import { useTabsDrag } from './use-tabs-drag';
import { useTabsViewScroll } from './use-tabs-view-scroll';
interface Props extends TabsProps {}
@ -24,136 +21,69 @@ const props = withDefaults(defineProps<Props>(), {
styleType: 'chrome',
});
const emit = defineEmits<{
close: [string];
sortTabs: [number, number];
unpin: [TabDefinition];
}>();
const emit = defineEmits<TabsEmits>();
const forward = useForwardPropsEmits(props, emit);
const { initScrollbar, scrollDirection } = useTabsViewScroll();
const sortableInstance = ref<null | Sortable>(null);
// domtab
function findParentElement(element: HTMLElement) {
const parentCls = 'group';
return element.classList.contains(parentCls)
? element
: element.closest(`.${parentCls}`);
}
async function initTabsSortable() {
await nextTick();
const { contentClass } = props;
const el = document.querySelectorAll(`.${contentClass}`)?.[0] as HTMLElement;
const resetElState = () => {
el.style.cursor = 'default';
el.classList.remove('dragging');
};
const { initializeSortable } = useSortable(el, {
filter: (_evt, target: HTMLElement) => {
const parent = findParentElement(target);
const dragable = parent?.classList.contains('dragable');
return !dragable || !props.dragable;
},
onEnd(evt) {
const { newIndex, oldIndex } = evt;
// const fromElement = evt.item;
const { srcElement } = (evt as any).originalEvent;
if (!srcElement) {
resetElState();
return;
}
const srcParent = findParentElement(srcElement);
if (!srcParent) {
resetElState();
return;
}
const {
handleScrollAt,
scrollbarRef,
scrollDirection,
scrollIsAtLeft,
scrollIsAtRight,
showScrollButton,
} = useTabsViewScroll(props);
if (!srcParent.classList.contains('dragable')) {
resetElState();
return;
}
if (
oldIndex !== undefined &&
newIndex !== undefined &&
!Number.isNaN(oldIndex) &&
!Number.isNaN(newIndex) &&
oldIndex !== newIndex
) {
emit('sortTabs', oldIndex, newIndex);
}
resetElState();
},
onMove(evt) {
const parent = findParentElement(evt.related);
return parent?.classList.contains('dragable') && props.dragable;
},
onStart: () => {
el.style.cursor = 'grabbing';
el.classList.add('dragging');
},
});
sortableInstance.value = await initializeSortable();
}
async function init() {
await nextTick();
initTabsSortable();
initScrollbar();
}
onMounted(() => {
init();
});
watch(
() => props.styleType,
() => {
sortableInstance.value?.destroy();
init();
},
);
onUnmounted(() => {
sortableInstance.value?.destroy();
});
useTabsDrag(props, emit);
</script>
<template>
<div
:class="{
'overflow-hidden': styleType !== 'chrome',
}"
class="flex h-full flex-1"
>
<div class="flex h-full flex-1 overflow-hidden">
<!-- 左侧滚动按钮 -->
<span
class="hover:bg-muted text-muted-foreground cursor-pointer border-r px-2"
v-show="showScrollButton"
:class="{
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtLeft,
'pointer-events-none opacity-30': scrollIsAtLeft,
}"
class="border-r px-2"
@click="scrollDirection('left')"
>
<ChevronLeft class="size-4 h-full" />
</span>
<TabsChrome
v-if="styleType === 'chrome'"
v-bind="{ ...forward, ...$attrs, ...$props }"
/>
<Tabs v-else v-bind="{ ...forward, ...$attrs, ...$props }" />
<div
:class="{
'pt-[3px]': styleType === 'chrome',
}"
class="size-full flex-1 overflow-hidden"
>
<VbenScrollbar
ref="scrollbarRef"
class="h-full"
horizontal
scroll-bar-class="z-10 hidden"
shadow
shadow-left
shadow-right
@scroll-at="handleScrollAt"
>
<TabsChrome
v-if="styleType === 'chrome'"
v-bind="{ ...forward, ...$attrs, ...$props }"
/>
<Tabs v-else v-bind="{ ...forward, ...$attrs, ...$props }" />
</VbenScrollbar>
</div>
<!-- 左侧滚动按钮 -->
<span
v-show="showScrollButton"
:class="{
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtRight,
'pointer-events-none opacity-30': scrollIsAtRight,
}"
class="hover:bg-muted text-muted-foreground cursor-pointer border-l px-2"
@click="scrollDirection('right')"
>

13
packages/@core/ui-kit/tabs-ui/src/types.ts

@ -1,7 +1,14 @@
import type { IContextMenuItem } from '@vben-core/shadcn-ui';
import type { TabDefinition, TabsStyleType } from '@vben-core/typings';
interface TabsProps {
export type TabsEmits = {
close: [string];
sortTabs: [number, number];
unpin: [TabDefinition];
};
export interface TabsProps {
active?: string;
/**
* @zh_CN content class
* @default tabs-chrome
@ -48,12 +55,10 @@ interface TabsProps {
tabs?: TabDefinition[];
}
interface TabConfig extends TabDefinition {
export interface TabConfig extends TabDefinition {
affixTab: boolean;
closable: boolean;
icon: string;
key: string;
title: string;
}
export type { TabConfig, TabsProps };

110
packages/@core/ui-kit/tabs-ui/src/use-tabs-drag.ts

@ -0,0 +1,110 @@
import type { EmitType } from '@vben-core/typings';
import type { TabsProps } from './types';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { type Sortable, useSortable } from '@vben-core/composables';
// 可能会找到拖拽的子元素,这里需要确保拖拽的dom时tab元素
function findParentElement(element: HTMLElement) {
const parentCls = 'group';
return element.classList.contains(parentCls)
? element
: element.closest(`.${parentCls}`);
}
export function useTabsDrag(props: TabsProps, emit: EmitType) {
const sortableInstance = ref<null | Sortable>(null);
async function initTabsSortable() {
await nextTick();
const el = document.querySelectorAll(
`.${props.contentClass}`,
)?.[0] as HTMLElement;
if (!el) {
console.warn('Element not found for sortable initialization');
return;
}
const resetElState = async () => {
el.style.cursor = 'default';
el.classList.remove('dragging');
el.querySelector('.draggable')?.classList.remove('dragging');
};
const { initializeSortable } = useSortable(el, {
filter: (_evt, target: HTMLElement) => {
const parent = findParentElement(target);
const dragable = parent?.classList.contains('dragable');
return !dragable || !props.dragable;
},
onEnd(evt) {
const { newIndex, oldIndex } = evt;
// const fromElement = evt.item;
const { srcElement } = (evt as any).originalEvent;
if (!srcElement) {
resetElState();
return;
}
const srcParent = findParentElement(srcElement);
if (!srcParent) {
resetElState();
return;
}
if (!srcParent.classList.contains('dragable')) {
resetElState();
return;
}
if (
oldIndex !== undefined &&
newIndex !== undefined &&
!Number.isNaN(oldIndex) &&
!Number.isNaN(newIndex) &&
oldIndex !== newIndex
) {
emit('sortTabs', oldIndex, newIndex);
}
resetElState();
},
onMove(evt) {
const parent = findParentElement(evt.related);
return parent?.classList.contains('dragable') && props.dragable;
},
onStart: () => {
el.style.cursor = 'grabbing';
el.querySelector('.draggable')?.classList.add('dragging');
// el.classList.add('dragging');
},
});
sortableInstance.value = await initializeSortable();
}
async function init() {
await nextTick();
initTabsSortable();
}
onMounted(init);
watch(
() => props.styleType,
() => {
sortableInstance.value?.destroy();
init();
},
);
onUnmounted(() => {
sortableInstance.value?.destroy();
});
}

160
packages/@core/ui-kit/tabs-ui/src/use-tabs-view-scroll.ts

@ -1,15 +1,28 @@
import { nextTick, ref } from 'vue';
import type { TabsProps } from './types';
type El = Element | null | undefined;
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
export function useTabsViewScroll(scrollDistance: number = 150) {
const scrollbarEl = ref<El>(null);
const scrollViewportEl = ref<El>(null);
import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { useDebounceFn } from '@vueuse/core';
type DomElement = Element | null | undefined;
export function useTabsViewScroll(props: TabsProps) {
let resizeObserver: null | ResizeObserver = null;
let mutationObserver: MutationObserver | null = null;
let tabItemCount = 0;
const scrollbarRef = ref<InstanceType<typeof VbenScrollbar> | null>(null);
const scrollViewportEl = ref<DomElement>(null);
const showScrollButton = ref(false);
const scrollIsAtLeft = ref(true);
const scrollIsAtRight = ref(false);
function getScrollClientWidth() {
if (!scrollbarEl.value || !scrollViewportEl.value) return {};
const scrollbarEl = scrollbarRef.value?.$el;
if (!scrollbarEl || !scrollViewportEl.value) return {};
const scrollbarWidth = scrollbarEl.value.clientWidth;
const scrollbarWidth = scrollbarEl.clientWidth;
const scrollViewWidth = scrollViewportEl.value.clientWidth;
return {
@ -20,7 +33,7 @@ export function useTabsViewScroll(scrollDistance: number = 150) {
function scrollDirection(
direction: 'left' | 'right',
distance: number = scrollDistance,
distance: number = 150,
) {
const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
@ -39,21 +52,142 @@ export function useTabsViewScroll(scrollDistance: number = 150) {
async function initScrollbar() {
await nextTick();
const barEl = document.querySelector('#tabs-scrollbar');
const viewportEl = barEl?.querySelector(
const scrollbarEl = scrollbarRef.value?.$el;
if (!scrollbarEl) {
return;
}
const viewportEl = scrollbarEl?.querySelector(
'div[data-radix-scroll-area-viewport]',
);
scrollbarEl.value = barEl;
scrollViewportEl.value = viewportEl;
calcShowScrollbarButton();
await nextTick();
scrollToActiveIntoView();
// 监听大小变化
resizeObserver?.disconnect();
resizeObserver = new ResizeObserver(
useDebounceFn((_entries: ResizeObserverEntry[]) => {
calcShowScrollbarButton();
}, 100),
);
resizeObserver.observe(viewportEl);
const activeItem = viewportEl?.querySelector('.is-active');
activeItem?.scrollIntoView({ behavior: 'smooth', block: 'start' });
tabItemCount = props.tabs?.length || 0;
mutationObserver?.disconnect();
// 使用 MutationObserver 仅监听子节点数量变化
mutationObserver = new MutationObserver(() => {
const count = viewportEl.querySelectorAll(
`div[data-tab-item="true"]`,
).length;
if (count > tabItemCount) {
scrollToActiveIntoView();
}
if (count !== tabItemCount) {
calcShowScrollbarButton();
tabItemCount = count;
}
});
// 配置为仅监听子节点的添加和移除
mutationObserver.observe(viewportEl, {
attributes: false,
childList: true,
subtree: true,
});
}
async function scrollToActiveIntoView() {
if (!scrollViewportEl.value) {
return;
}
await nextTick();
const viewportEl = scrollViewportEl.value;
const { scrollbarWidth } = getScrollClientWidth();
const { scrollWidth } = viewportEl;
if (scrollbarWidth >= scrollWidth) {
return;
}
requestAnimationFrame(() => {
const activeItem = viewportEl?.querySelector('.is-active');
activeItem?.scrollIntoView({ behavior: 'smooth', inline: 'start' });
});
}
/**
* tabs
*/
async function calcShowScrollbarButton() {
if (!scrollViewportEl.value) {
return;
}
const { scrollbarWidth } = getScrollClientWidth();
showScrollButton.value =
scrollViewportEl.value.scrollWidth > scrollbarWidth;
}
const handleScrollAt = useDebounceFn(({ left, right }) => {
scrollIsAtLeft.value = left;
scrollIsAtRight.value = right;
}, 100);
watch(
() => props.active,
async () => {
// 200为了等待 tab 切换动画完成
// setTimeout(() => {
scrollToActiveIntoView();
// }, 300);
},
{
flush: 'post',
},
);
// watch(
// () => props.tabs?.length,
// async () => {
// await nextTick();
// calcShowScrollbarButton();
// },
// {
// flush: 'post',
// },
// );
watch(
() => props.styleType,
() => {
initScrollbar();
},
);
onMounted(initScrollbar);
onUnmounted(() => {
resizeObserver?.disconnect();
mutationObserver?.disconnect();
resizeObserver = null;
mutationObserver = null;
});
return {
handleScrollAt,
initScrollbar,
scrollbarRef,
scrollDirection,
scrollIsAtLeft,
scrollIsAtRight,
showScrollButton,
};
}

2
packages/effects/access/package.json

@ -24,6 +24,6 @@
"@vben/stores": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"vue": "^3.4.38"
"vue": "^3.4.37"
}
}

4
packages/effects/chart-ui/package.json

@ -21,8 +21,8 @@
},
"dependencies": {
"@vben/preferences": "workspace:*",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"echarts": "^5.5.1",
"vue": "^3.4.38"
"vue": "^3.4.37"
}
}

4
packages/effects/common-ui/package.json

@ -26,9 +26,9 @@
"@vben/icons": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/types": "workspace:*",
"@vueuse/integrations": "^10.11.1",
"@vueuse/integrations": "^11.0.0",
"qrcode": "^1.5.4",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
},
"devDependencies": {

2
packages/effects/hooks/package.json

@ -25,7 +25,7 @@
"@vben/stores": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3",
"watermark-js-plus": "^1.5.3"
}

4
packages/effects/layouts/package.json

@ -32,8 +32,8 @@
"@vben/stores": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.1",
"vue": "^3.4.38",
"@vueuse/core": "^11.0.0",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
}
}

2
packages/locales/package.json

@ -21,7 +21,7 @@
},
"dependencies": {
"@intlify/core-base": "^9.13.1",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-i18n": "^9.13.1"
}
}

2
packages/stores/package.json

@ -24,7 +24,7 @@
"@vben-core/typings": "workspace:*",
"pinia": "2.2.2",
"pinia-plugin-persistedstate": "^3.2.1",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
}
}

17
packages/stores/src/modules/tabbar.ts

@ -124,10 +124,21 @@ export const useTabbarStore = defineStore('core-tabbar', {
} else {
// 页面已经存在,不重复添加选项卡,只更新选项卡参数
const currentTab = toRaw(this.tabs)[tabIndex];
const mergedTab = { ...currentTab, ...tab };
if (currentTab && Reflect.has(currentTab.meta, 'affixTab')) {
mergedTab.meta.affixTab = currentTab.meta.affixTab;
const mergedTab = {
...currentTab,
...tab,
meta: { ...currentTab?.meta, ...tab.meta },
};
if (currentTab) {
const curMeta = currentTab.meta;
if (Reflect.has(curMeta, 'affixTab')) {
mergedTab.meta.affixTab = curMeta.affixTab;
}
if (Reflect.has(curMeta, 'newTabTitle')) {
mergedTab.meta.newTabTitle = curMeta.newTabTitle;
}
}
this.tabs.splice(tabIndex, 1, mergedTab);
}
this.updateCacheTab();

2
packages/types/package.json

@ -21,7 +21,7 @@
},
"dependencies": {
"@vben-core/typings": "workspace:*",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
}
}

4
playground/package.json

@ -40,11 +40,11 @@
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"ant-design-vue": "^4.2.3",
"dayjs": "^1.11.12",
"pinia": "2.2.2",
"vue": "^3.4.38",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
}
}

2
playground/src/router/routes/modules/demos.ts

@ -161,7 +161,7 @@ const routes: RouteRecordRaw[] = [
import(
'#/views/demos/features/hide-menu-children/children.vue'
),
meta: { title: 'HideChildrenInMenuChildrenDemo' },
meta: { title: $t('page.demos.features.hideChildrenInMenu') },
},
],
},

115
pnpm-lock.yaml

@ -165,8 +165,8 @@ importers:
specifier: workspace:*
version: link:../../packages/utils
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
ant-design-vue:
specifier: ^4.2.3
version: 4.2.3(vue@3.4.38(typescript@5.5.4))
@ -228,8 +228,8 @@ importers:
specifier: workspace:*
version: link:../../packages/utils
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
dayjs:
specifier: ^1.11.12
version: 1.11.12
@ -295,8 +295,8 @@ importers:
specifier: workspace:*
version: link:../../packages/utils
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
naive-ui:
specifier: ^2.39.0
version: 2.39.0(vue@3.4.38(typescript@5.5.4))
@ -660,8 +660,8 @@ importers:
specifier: ^4.1.2
version: 4.1.2(vue@3.4.38(typescript@5.5.4))
lucide-vue-next:
specifier: ^0.427.0
version: 0.427.0(vue@3.4.38(typescript@5.5.4))
specifier: ^0.428.0
version: 0.428.0(vue@3.4.38(typescript@5.5.4))
vue:
specifier: 3.4.38
version: 3.4.38(typescript@5.5.4)
@ -672,7 +672,7 @@ importers:
specifier: 4.1.0
version: 4.1.0
'@vue/shared':
specifier: ^3.4.38
specifier: ^3.4.37
version: 3.4.38
clsx:
specifier: 2.1.1
@ -715,8 +715,8 @@ importers:
specifier: workspace:*
version: link:../base/shared
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
radix-vue:
specifier: ^1.9.4
version: 1.9.4(vue@3.4.38(typescript@5.5.4))
@ -740,8 +740,8 @@ importers:
specifier: workspace:*
version: link:../base/typings
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
vue:
specifier: 3.4.38
version: 3.4.38(typescript@5.5.4)
@ -761,8 +761,8 @@ importers:
specifier: workspace:*
version: link:../../base/typings
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
vue:
specifier: 3.4.38
version: 3.4.38(typescript@5.5.4)
@ -785,8 +785,8 @@ importers:
specifier: workspace:*
version: link:../../base/typings
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
vue:
specifier: 3.4.38
version: 3.4.38(typescript@5.5.4)
@ -806,14 +806,14 @@ importers:
specifier: workspace:*
version: link:../../base/typings
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
lucide-vue-next:
specifier: ^0.427.0
version: 0.427.0(vue@3.4.38(typescript@5.5.4))
specifier: ^0.428.0
version: 0.428.0(vue@3.4.38(typescript@5.5.4))
radix-vue:
specifier: ^1.9.4
version: 1.9.4(vue@3.4.38(typescript@5.5.4))
@ -835,6 +835,9 @@ importers:
'@vben-core/typings':
specifier: workspace:*
version: link:../../base/typings
'@vueuse/core':
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
vue:
specifier: 3.4.38
version: 3.4.38(typescript@5.5.4)
@ -869,8 +872,8 @@ importers:
specifier: workspace:*
version: link:../../preferences
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
echarts:
specifier: ^5.5.1
version: 5.5.1
@ -899,8 +902,8 @@ importers:
specifier: workspace:*
version: link:../../types
'@vueuse/integrations':
specifier: ^10.11.1
version: 10.11.1(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))
qrcode:
specifier: ^1.5.4
version: 1.5.4
@ -981,8 +984,8 @@ importers:
specifier: workspace:*
version: link:../../utils
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
vue:
specifier: 3.4.38
version: 3.4.38(typescript@5.5.4)
@ -1129,8 +1132,8 @@ importers:
specifier: workspace:*
version: link:../packages/utils
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
specifier: ^11.0.0
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
ant-design-vue:
specifier: ^4.2.3
version: 4.2.3(vue@3.4.38(typescript@5.5.4))
@ -4179,6 +4182,9 @@ packages:
'@vueuse/core@10.11.1':
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
'@vueuse/core@11.0.0':
resolution: {integrity: sha512-shibzNGjmRjZucEm97B8V0NO5J3vPHMCE/mltxQ3vHezbDoFQBMtK11XsfwfPionxSbo+buqPmsCljtYuXIBpw==}
'@vueuse/core@9.13.0':
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
@ -4223,21 +4229,21 @@ packages:
universal-cookie:
optional: true
'@vueuse/integrations@10.11.1':
resolution: {integrity: sha512-Y5hCGBguN+vuVYTZmdd/IMXLOdfS60zAmDmFYc4BKBcMUPZH1n4tdyDECCPjXm0bNT3ZRUy1xzTLGaUje8Xyaw==}
'@vueuse/integrations@11.0.0':
resolution: {integrity: sha512-B95nBX4B2q2ZETBDldrKARM/fYXBHfwdo44UbHBq4bUTi25lrlc8MwAZGqEoRvdV4ND9T6O1Rb9e4kaCJFXnqw==}
peerDependencies:
async-validator: ^4
axios: ^1
change-case: ^4
drauu: ^0.3
change-case: ^5
drauu: ^0.4
focus-trap: ^7
fuse.js: ^6
fuse.js: ^7
idb-keyval: ^6
jwt-decode: ^3
jwt-decode: ^4
nprogress: ^0.2
qrcode: ^1.5
sortablejs: ^1
universal-cookie: ^6
universal-cookie: ^7
peerDependenciesMeta:
async-validator:
optional: true
@ -4270,6 +4276,9 @@ packages:
'@vueuse/metadata@10.11.1':
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
'@vueuse/metadata@11.0.0':
resolution: {integrity: sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==}
'@vueuse/metadata@9.13.0':
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
@ -4279,6 +4288,9 @@ packages:
'@vueuse/shared@10.11.1':
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
'@vueuse/shared@11.0.0':
resolution: {integrity: sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==}
'@vueuse/shared@9.13.0':
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
@ -6916,8 +6928,8 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
lucide-vue-next@0.427.0:
resolution: {integrity: sha512-zI1FhbfQ3Wl0SgPKnOWhTDC6yAC5TTjSC9FSZ61ULg3U36e+GVK+RT1qfkU9Q5BjeBuwmsHWKsXKptKMjUAwFA==}
lucide-vue-next@0.428.0:
resolution: {integrity: sha512-of9GJGus9VKGIUOp3yQ0uQtNv+8MRLaso8H4OiDzI6+T7TeMRXTzqVOLhnyg9fdXUnYuwE9Xm1zD1nfQ7oFPmg==}
peerDependencies:
vue: 3.4.38
@ -13196,6 +13208,16 @@ snapshots:
- '@vue/composition-api'
- vue
'@vueuse/core@11.0.0(vue@3.4.38(typescript@5.5.4))':
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 11.0.0
'@vueuse/shared': 11.0.0(vue@3.4.38(typescript@5.5.4))
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/core@9.13.0(vue@3.4.38(typescript@5.5.4))':
dependencies:
'@types/web-bluetooth': 0.0.16
@ -13222,10 +13244,10 @@ snapshots:
- '@vue/composition-api'
- vue
'@vueuse/integrations@10.11.1(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))':
'@vueuse/integrations@11.0.0(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))':
dependencies:
'@vueuse/core': 10.11.1(vue@3.4.38(typescript@5.5.4))
'@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.5.4))
'@vueuse/core': 11.0.0(vue@3.4.38(typescript@5.5.4))
'@vueuse/shared': 11.0.0(vue@3.4.38(typescript@5.5.4))
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
optionalDependencies:
async-validator: 4.2.5
@ -13242,6 +13264,8 @@ snapshots:
'@vueuse/metadata@10.11.1': {}
'@vueuse/metadata@11.0.0': {}
'@vueuse/metadata@9.13.0': {}
'@vueuse/shared@10.11.0(vue@3.4.38(typescript@5.5.4))':
@ -13258,6 +13282,13 @@ snapshots:
- '@vue/composition-api'
- vue
'@vueuse/shared@11.0.0(vue@3.4.38(typescript@5.5.4))':
dependencies:
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/shared@9.13.0(vue@3.4.38(typescript@5.5.4))':
dependencies:
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
@ -16250,7 +16281,7 @@ snapshots:
dependencies:
yallist: 4.0.0
lucide-vue-next@0.427.0(vue@3.4.38(typescript@5.5.4)):
lucide-vue-next@0.428.0(vue@3.4.38(typescript@5.5.4)):
dependencies:
vue: 3.4.38(typescript@5.5.4)

Loading…
Cancel
Save