Browse Source

perf: optimization of tabbar display (#4169)

* perf: optimization of tabbar display

* fix: ci error

* chore: typo

* chore: typo
pull/4171/head
Vben 2 years 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/styles": "workspace:*",
"@vben/types": "workspace:*", "@vben/types": "workspace:*",
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.1", "@vueuse/core": "^11.0.0",
"ant-design-vue": "^4.2.3", "ant-design-vue": "^4.2.3",
"dayjs": "^1.11.12", "dayjs": "^1.11.12",
"pinia": "2.2.2", "pinia": "2.2.2",
"vue": "^3.4.38", "vue": "^3.4.37",
"vue-router": "^4.4.3" "vue-router": "^4.4.3"
} }
} }

4
apps/web-ele/package.json

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

4
apps/web-naive/package.json

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

2
docs/package.json

@ -14,6 +14,6 @@
"@nolebase/vitepress-plugin-git-changelog": "^2.4.0", "@nolebase/vitepress-plugin-git-changelog": "^2.4.0",
"@vite-pwa/vitepress": "^0.5.0", "@vite-pwa/vitepress": "^0.5.0",
"vitepress": "^1.3.2", "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-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': [ '@typescript-eslint/no-empty-function': [

2
package.json

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

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

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

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

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

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

@ -38,7 +38,7 @@
} }
}, },
"dependencies": { "dependencies": {
"vue": "^3.4.38", "vue": "^3.4.37",
"vue-router": "^4.4.3" "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>> ? MergeAll<Rest, Merge<R, F>>
: R; : R;
export { type EmitType = (name: Name, ...args: any[]) => void;
type AnyFunction,
type AnyNormalFunction, export type {
type AnyPromiseFunction, AnyFunction,
type DeepPartial, AnyNormalFunction,
type DeepReadonly, AnyPromiseFunction,
type IntervalHandle, DeepPartial,
type MaybeComputedRef, DeepReadonly,
type MaybeReadonlyRef, EmitType,
type Merge, IntervalHandle,
type MergeAll, MaybeComputedRef,
type NonNullable, MaybeReadonlyRef,
type Nullable, Merge,
type ReadonlyRecordable, MergeAll,
type Recordable, NonNullable,
type TimeoutHandle, Nullable,
ReadonlyRecordable,
Recordable,
TimeoutHandle,
}; };

4
packages/@core/composables/package.json

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

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

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

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

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

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

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

4
packages/@core/preferences/package.json

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

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

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

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

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

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

@ -46,10 +46,10 @@
"@vben-core/icons": "workspace:*", "@vben-core/icons": "workspace:*",
"@vben-core/shared": "workspace:*", "@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*", "@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.1", "@vueuse/core": "^11.0.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"lucide-vue-next": "^0.427.0", "lucide-vue-next": "^0.428.0",
"radix-vue": "^1.9.4", "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"> <script setup lang="ts">
import { ref } from 'vue'; import { computed, ref } from 'vue';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
@ -11,6 +11,10 @@ interface Props {
scrollBarClass?: any; scrollBarClass?: any;
shadow?: boolean; shadow?: boolean;
shadowBorder?: boolean; shadowBorder?: boolean;
shadowBottom?: boolean;
shadowLeft?: boolean;
shadowRight?: boolean;
shadowTop?: boolean;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -18,29 +22,66 @@ const props = withDefaults(defineProps<Props>(), {
horizontal: false, horizontal: false,
shadow: false, shadow: false,
shadowBorder: 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 isAtTop = ref(true);
const isAtRight = ref(false);
const isAtBottom = 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) { function handleScroll(event: Event) {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
const scrollTop = target?.scrollTop ?? 0; const scrollTop = target?.scrollTop ?? 0;
const scrollLeft = target?.scrollLeft ?? 0;
const offsetHeight = target?.offsetHeight ?? 0; const offsetHeight = target?.offsetHeight ?? 0;
const offsetWidth = target?.offsetWidth ?? 0;
const scrollHeight = target?.scrollHeight ?? 0; const scrollHeight = target?.scrollHeight ?? 0;
const scrollWidth = target?.scrollWidth ?? 0;
isAtTop.value = scrollTop <= 0; isAtTop.value = scrollTop <= 0;
isAtLeft.value = scrollLeft <= 0;
isAtBottom.value = scrollTop + offsetHeight >= scrollHeight; 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> </script>
<template> <template>
<ScrollArea <ScrollArea
:class="[cn(props.class)]" :class="[cn(props.class), computedShadowClasses]"
:on-scroll="handleScroll" :on-scroll="handleScroll"
class="relative" class="vben-scrollbar relative"
> >
<div <div
v-if="shadow" v-if="showShadowTop"
:class="{ :class="{
'opacity-100': !isAtTop, 'opacity-100': !isAtTop,
'border-border border-t': shadowBorder && !isAtTop, 'border-border border-t': shadowBorder && !isAtTop,
@ -49,7 +90,7 @@ function handleScroll(event: Event) {
></div> ></div>
<slot></slot> <slot></slot>
<div <div
v-if="shadow" v-if="showShadowBottom"
:class="{ :class="{
'opacity-100': !isAtTop && !isAtBottom, 'opacity-100': !isAtTop && !isAtBottom,
'border-border border-b': shadowBorder && !isAtTop && !isAtBottom, 'border-border border-b': shadowBorder && !isAtTop && !isAtBottom,
@ -65,6 +106,31 @@ function handleScroll(event: Event) {
</template> </template>
<style scoped> <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 { .scrollbar-top-shadow {
background: linear-gradient( background: linear-gradient(
to bottom, to bottom,

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

@ -41,6 +41,7 @@
"@vben-core/icons": "workspace:*", "@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*", "@vben-core/shadcn-ui": "workspace:*",
"@vben-core/typings": "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 type { TabConfig, TabsProps } from '../../types';
import { computed, ref, watch } from 'vue'; import { computed, ref } from 'vue';
import { MdiPin, X } from '@vben-core/icons'; 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 {} interface Props extends TabsProps {}
@ -20,17 +20,17 @@ const props = withDefaults(defineProps<Props>(), {
contentClass: 'vben-tabs-content', contentClass: 'vben-tabs-content',
contextMenus: () => [], contextMenus: () => [],
gap: 7, gap: 7,
maxWidth: 150,
minWidth: 80,
tabs: () => [], tabs: () => [],
}); });
const emit = defineEmits<{ close: [string]; unpin: [TabDefinition] }>(); const emit = defineEmits<{
close: [string];
unpin: [TabDefinition];
}>();
const active = defineModel<string>('active'); const active = defineModel<string>('active');
const contentRef = ref(); const contentRef = ref();
const tabRef = ref(); const tabRef = ref();
const tabWidth = ref<number>(props.maxWidth);
const style = computed(() => { const style = computed(() => {
const { gap } = props; 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> </script>
<template> <template>
<div :style="style" class="tabs-chrome size-full flex-1 overflow-hidden pt-1"> <div
<VbenScrollbar ref="contentRef"
id="tabs-scrollbar" :class="contentClass"
class="tabs-chrome__scrollbar h-full" :style="style"
horizontal class="tabs-chrome !flex h-full w-max pr-6"
scroll-bar-class="z-10 hidden" >
> <TransitionGroup name="slide-left">
<!-- footer -> 4px -->
<div <div
ref="contentRef" v-for="(tab, i) in tabsView"
:class="contentClass" :key="tab.key"
class="relative !flex h-full w-max" 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"> <VbenContextMenu
<div :handler-data="tab"
v-for="(tab, i) in tabsView" :menus="contextMenus"
:key="tab.key" :modal="false"
ref="tabRef" item-class="pr-6"
:class="[ >
{ 'is-active': tab.key === active, dragable: !tab.affixTab }, <div class="relative size-full px-1">
]" <!-- divider -->
:data-active-tab="active" <div
:data-index="i" v-if="i !== 0 && tab.key !== active"
:style="{ 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"
width: `${tabWidth}px`, ></div>
left: `${(tabWidth - gap * 2) * i}px`, <!-- background -->
}" <div
class="tabs-chrome__item group absolute flex h-full select-none items-center transition-all" class="tabs-chrome__background absolute z-[-1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
@click="active = tab.key"
>
<VbenContextMenu
:handler-data="tab"
:menus="contextMenus"
:modal="false"
item-class="pr-6"
> >
<div class="size-full"> <div
<!-- divider --> 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 ></div>
v-if="i !== 0 && tab.key !== active" <svg
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" 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"
></div> height="7"
<!-- background --> width="7"
<div >
class="tabs-chrome__background absolute z-[1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150" <path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
> </svg>
<div <svg
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" 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"
></div> height="7"
<svg width="7"
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" <path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
width="7" </svg>
> </div>
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
</svg> <!-- extra -->
<svg <div
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" class="tabs-chrome__extra absolute right-[var(--gap)] top-1/2 z-[3] size-4 translate-y-[-50%]"
height="7" >
width="7" <!-- close-icon -->
> <X
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" /> v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
</svg> 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"
</div> @click.stop="() => emit('close', tab.key)"
/>
<!-- extra --> <MdiPin
<div v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="tabs-chrome__extra absolute right-[calc(var(--gap)*1.5)] top-1/2 z-[3] size-4 translate-y-[-50%]" 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)"
<!-- close-icon --> />
<X </div>
v-show="
!tab.affixTab && tabsView.length > 1 && tab.closable <!-- tab-item-main -->
" <div
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" 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"
@click.stop="() => emit('close', tab.key)" >
/> <VbenIcon
<MdiPin v-if="showIcon"
v-show="tab.affixTab && tabsView.length > 1 && tab.closable" :icon="tab.icon"
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" class="mr-1 flex size-4 items-center overflow-hidden"
@click.stop="() => emit('unpin', tab)" fallback
/> />
</div>
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
<!-- tab-item-main --> {{ tab.title }}
<div </span>
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" </div>
>
<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> </div>
</TransitionGroup> </VbenContextMenu>
</div> </div>
<!-- footer --> </TransitionGroup>
<!-- <div class="bg-background h-1"></div> -->
</VbenScrollbar>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.tabs-chrome { .tabs-chrome {
.dragging { /* .dragging { */
.tabs-chrome__item-main {
/* .tabs-chrome__item-main {
@apply pr-0; @apply pr-0;
} } */
.tabs-chrome__extra { /* .tabs-chrome__extra {
@apply hidden; @apply hidden;
} } */
}
/* } */
&__item:not(.dragging) {
@apply cursor-pointer;
&__item {
&:hover:not(.is-active) { &:hover:not(.is-active) {
& + .tabs-chrome__item { & + .tabs-chrome__item {
.tabs-chrome__divider { .tabs-chrome__divider {
@ -207,13 +177,10 @@ function scrollIntoView() {
} }
.tabs-chrome__background { .tabs-chrome__background {
&-content { @apply pb-[2px];
@apply bg-accent mx-1 rounded-md pb-2;
}
&-before, &-content {
&-after { @apply bg-accent-hover mx-[2px] rounded-md;
@apply fill-primary/0;
} }
} }
} }
@ -226,30 +193,7 @@ function scrollIntoView() {
@apply opacity-0 !important; @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> </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 type { TabConfig, TabsProps } from '../../types';
import { computed, watch } from 'vue'; import { computed } from 'vue';
import { MdiPin, X } from '@vben-core/icons'; 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 {} interface Props extends TabsProps {}
@ -21,7 +21,10 @@ const props = withDefaults(defineProps<Props>(), {
tabs: () => [], tabs: () => [],
}); });
const emit = defineEmits<{ close: [string]; unpin: [TabDefinition] }>(); const emit = defineEmits<{
close: [string];
unpin: [TabDefinition];
}>();
const active = defineModel<string>('active'); const active = defineModel<string>('active');
const typeWithClass = computed(() => { 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> </script>
<template> <template>
<div class="size-full flex-1 overflow-hidden"> <div
<VbenScrollbar :class="contentClass"
id="tabs-scrollbar" class="relative !flex h-full w-max items-center pr-6"
class="tabs-scrollbar h-full" >
horizontal <TransitionGroup name="slide-left">
scroll-bar-class="z-10 hidden"
>
<div <div
:class="contentClass" v-for="(tab, i) in tabsView"
class="relative !flex h-full w-max items-center" :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"> <VbenContextMenu
<div :handler-data="tab"
v-for="(tab, i) in tabsView" :menus="contextMenus"
:key="tab.key" :modal="false"
:class="[ item-class="pr-6"
{ >
'is-active dark:bg-accent bg-primary/15': tab.key === active, <div class="relative flex size-full items-center">
dragable: !tab.affixTab, <!-- extra -->
}, <div
typeWithClass.content, class="absolute right-1.5 top-1/2 z-[3] translate-y-[-50%] overflow-hidden"
]"
: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"
> >
<div class="relative flex size-full items-center"> <!-- close-icon -->
<!-- extra --> <X
<div v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
class="absolute right-1.5 top-1/2 z-[3] translate-y-[-50%] overflow-hidden" 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)"
<!-- close-icon --> />
<X <MdiPin
v-show=" v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
!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)"
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)" </div>
/>
<MdiPin <!-- tab-item-main -->
v-show="tab.affixTab && tabsView.length > 1 && tab.closable" <div
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" 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"
@click.stop="() => emit('unpin', tab)" >
/> <VbenIcon
</div> v-if="showIcon"
:icon="tab.icon"
<!-- tab-item-main --> class="mr-2 flex size-4 items-center overflow-hidden"
<div fallback
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 <span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
v-if="showIcon" {{ tab.title }}
:icon="tab.icon" </span>
class="mr-2 flex size-4 items-center overflow-hidden" </div>
fallback
/>
<span
class="flex-1 overflow-hidden whitespace-nowrap text-sm"
>
{{ tab.title }}
</span>
</div>
</div>
</VbenContextMenu>
</div> </div>
</TransitionGroup> </VbenContextMenu>
</div> </div>
</VbenScrollbar> </TransitionGroup>
</div> </div>
</template> </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"> <script setup lang="ts">
import type { Sortable } from '@vben-core/composables'; import type { TabsEmits, TabsProps } from './types';
import type { TabDefinition } from '@vben-core/typings';
import type { TabsProps } from './types'; import { useForwardPropsEmits } from '@vben-core/composables';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { useForwardPropsEmits, useSortable } from '@vben-core/composables';
import { ChevronLeft, ChevronRight } from '@vben-core/icons'; import { ChevronLeft, ChevronRight } from '@vben-core/icons';
import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { Tabs, TabsChrome } from './components'; import { Tabs, TabsChrome } from './components';
import { useTabsDrag } from './use-tabs-drag';
import { useTabsViewScroll } from './use-tabs-view-scroll'; import { useTabsViewScroll } from './use-tabs-view-scroll';
interface Props extends TabsProps {} interface Props extends TabsProps {}
@ -24,136 +21,69 @@ const props = withDefaults(defineProps<Props>(), {
styleType: 'chrome', styleType: 'chrome',
}); });
const emit = defineEmits<{ const emit = defineEmits<TabsEmits>();
close: [string];
sortTabs: [number, number];
unpin: [TabDefinition];
}>();
const forward = useForwardPropsEmits(props, emit); const forward = useForwardPropsEmits(props, emit);
const { initScrollbar, scrollDirection } = useTabsViewScroll(); const {
handleScrollAt,
const sortableInstance = ref<null | Sortable>(null); scrollbarRef,
scrollDirection,
// domtab scrollIsAtLeft,
function findParentElement(element: HTMLElement) { scrollIsAtRight,
const parentCls = 'group'; showScrollButton,
return element.classList.contains(parentCls) } = useTabsViewScroll(props);
? 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;
}
if (!srcParent.classList.contains('dragable')) { useTabsDrag(props, emit);
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();
});
</script> </script>
<template> <template>
<div <div class="flex h-full flex-1 overflow-hidden">
:class="{
'overflow-hidden': styleType !== 'chrome',
}"
class="flex h-full flex-1"
>
<!-- 左侧滚动按钮 --> <!-- 左侧滚动按钮 -->
<span <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')" @click="scrollDirection('left')"
> >
<ChevronLeft class="size-4 h-full" /> <ChevronLeft class="size-4 h-full" />
</span> </span>
<TabsChrome <div
v-if="styleType === 'chrome'" :class="{
v-bind="{ ...forward, ...$attrs, ...$props }" 'pt-[3px]': styleType === 'chrome',
/> }"
<Tabs v-else v-bind="{ ...forward, ...$attrs, ...$props }" /> 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 <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" class="hover:bg-muted text-muted-foreground cursor-pointer border-l px-2"
@click="scrollDirection('right')" @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 { IContextMenuItem } from '@vben-core/shadcn-ui';
import type { TabDefinition, TabsStyleType } from '@vben-core/typings'; 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 * @zh_CN content class
* @default tabs-chrome * @default tabs-chrome
@ -48,12 +55,10 @@ interface TabsProps {
tabs?: TabDefinition[]; tabs?: TabDefinition[];
} }
interface TabConfig extends TabDefinition { export interface TabConfig extends TabDefinition {
affixTab: boolean; affixTab: boolean;
closable: boolean; closable: boolean;
icon: string; icon: string;
key: string; key: string;
title: 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) { import { VbenScrollbar } from '@vben-core/shadcn-ui';
const scrollbarEl = ref<El>(null);
const scrollViewportEl = ref<El>(null); 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() { 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; const scrollViewWidth = scrollViewportEl.value.clientWidth;
return { return {
@ -20,7 +33,7 @@ export function useTabsViewScroll(scrollDistance: number = 150) {
function scrollDirection( function scrollDirection(
direction: 'left' | 'right', direction: 'left' | 'right',
distance: number = scrollDistance, distance: number = 150,
) { ) {
const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth(); const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
@ -39,21 +52,142 @@ export function useTabsViewScroll(scrollDistance: number = 150) {
async function initScrollbar() { async function initScrollbar() {
await nextTick(); 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]', 'div[data-radix-scroll-area-viewport]',
); );
scrollbarEl.value = barEl;
scrollViewportEl.value = viewportEl; 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'); tabItemCount = props.tabs?.length || 0;
activeItem?.scrollIntoView({ behavior: 'smooth', block: 'start' }); 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 { return {
handleScrollAt,
initScrollbar, initScrollbar,
scrollbarRef,
scrollDirection, scrollDirection,
scrollIsAtLeft,
scrollIsAtRight,
showScrollButton,
}; };
} }

2
packages/effects/access/package.json

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

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

@ -21,8 +21,8 @@
}, },
"dependencies": { "dependencies": {
"@vben/preferences": "workspace:*", "@vben/preferences": "workspace:*",
"@vueuse/core": "^10.11.1", "@vueuse/core": "^11.0.0",
"echarts": "^5.5.1", "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/icons": "workspace:*",
"@vben/locales": "workspace:*", "@vben/locales": "workspace:*",
"@vben/types": "workspace:*", "@vben/types": "workspace:*",
"@vueuse/integrations": "^10.11.1", "@vueuse/integrations": "^11.0.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"vue": "^3.4.38", "vue": "^3.4.37",
"vue-router": "^4.4.3" "vue-router": "^4.4.3"
}, },
"devDependencies": { "devDependencies": {

2
packages/effects/hooks/package.json

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

4
packages/effects/layouts/package.json

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

2
packages/locales/package.json

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

2
packages/stores/package.json

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

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

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

2
packages/types/package.json

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

4
playground/package.json

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

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

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

Loading…
Cancel
Save